Simple Lightbox - Version 1.6

Version Description

  • Add: Widget support
  • Add: WordPress 3.3 support
  • Add: Localization support
  • Add: Option to group gallery links separately (supports WordPress & NextGen galleries)
  • Add: Upgrade notice
  • Optimize: WP 3.3 compatibility
  • Optimize: Improved compatibility with URI case-sensitivity
  • Optimize: Activation processing
  • Optimize: Image grouping
  • Optimize: Image metadata loading performance
  • Optimize: File loading
  • Optimize: Improved safeguards against interference by bugs in other plugins
  • Optimize: Link processing performance
  • Optimize: Lightbox styling isolated from site styles
  • Optimize: Improved link processing performance
  • Optimize: Improved image metadata support
  • Optimize: Improved support for HTTP/HTTPS requests
  • Fix: SLB is not defined in JS (Jezz Hands)
  • Fix: Boolean case-sensitivity (78 Truths)
  • Fix: YouTube embed using iFrame overlaps lightbox (Elena in Hiding)
  • Fix: Issue when scanning links without valid URLs (McCloskey Iteration)
  • Fix: Image activation is case-sensitive (Sensitive Tanya)
  • Fix: Visible lightbox overlay edges when image larger than browser window (Chibi Overlay)
  • Fix: Options availability for some users
  • Fix: Inconsistent loading of image metadata
  • Fix: Links not fully processed when group is set manually
Download this release

Release Info

Developer Archetyped
Plugin Icon wp plugin Simple Lightbox
Version 1.6
Comparing to
See all releases

Code changes from version 1.3 to 1.6

css/admin.css CHANGED
@@ -1,3 +1,33 @@
1
  .subhead {
2
  margin-bottom: .2em;
3
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .subhead {
2
  margin-bottom: .2em;
3
  }
4
+
5
+ #slb_settings {
6
+ display: block;
7
+ padding: 2em 0 0;
8
+ }
9
+
10
+ .slb_option_item .block {
11
+ display: inline-block;
12
+ }
13
+
14
+ .slb_option_item label.title {
15
+ width: 200px;
16
+ padding: 10px;
17
+ }
18
+
19
+ .slb_option_item .input {
20
+ font-size: 11px;
21
+ line-height: 20px;
22
+ margin-bottom: 9px;
23
+ padding: 8px 10px;
24
+ }
25
+
26
+ .slb_option_item .input select {
27
+ min-width: 12em;
28
+ }
29
+
30
+ .slb_notice {
31
+ color: #f00;
32
+ font-weight: bold;
33
+ }
css/lb_black.css ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('lightbox.css');
2
+
3
+ /* General */
4
+
5
+ #slb_viewer {
6
+ color:#fff;
7
+ }
8
+
9
+ #slb_viewer a,
10
+ #slb_viewer a:hover {
11
+ color:#fff;
12
+ }
13
+
14
+ #slb_container {
15
+ background-color: #000;
16
+ }
17
+
18
+ #slb_slbLoading {
19
+ background-image: url("../images/loading_black.gif");
20
+ }
21
+
22
+ /* Navigation */
23
+
24
+ #slb_nav_hover .slb_navPrev:hover,
25
+ #slb_nav_hover .slb_navPrev:visited:hover {
26
+ background-image: url("../images/prevlabel_black.gif");
27
+ }
28
+
29
+ #slb_nav_hover .slb_navNext:hover,
30
+ #slb_nav_hover .slb_navNext:visited:hover {
31
+ background-image: url("../images/nextlabel_black.gif");
32
+ }
33
+
34
+ #slb_close .slb_slbClose {
35
+ background-image: url("../images/closelabel_black.gif");
36
+ }
37
+
38
+ /* Content */
39
+
40
+ #slb_details {
41
+ background-color: #000;
42
+ }
43
+
css/lightbox.css CHANGED
@@ -1,35 +1,61 @@
1
- #lightbox{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  position: absolute;
3
  top: 20px;
4
  left: 0;
5
  width: 100%;
6
- z-index: 3100;
7
  text-align: center;
8
  line-height: 0;
9
  color:#151410;
10
- }
11
 
12
- #lightbox a, #lightbox a:hover {
 
13
  border-bottom:none;
14
  color:#151410;
15
  text-decoration:underline;
16
  }
17
 
18
- #lightbox a img{ border: none; }
 
 
19
 
20
- #outerImageContainer{
21
  position: relative;
22
  background-color: #fff;
23
  width: 250px;
24
  height: 250px;
25
  margin: 0 auto;
26
- }
27
 
28
- #imageContainer{
29
  padding: 10px;
30
- }
31
 
32
- #loading{
33
  position: absolute;
34
  top: 40%;
35
  left: 0%;
@@ -37,8 +63,9 @@
37
  width: 100%;
38
  text-align: center;
39
  line-height: 0;
40
- }
41
- #loadingLink {
 
42
  display:block;
43
  margin:0 auto;
44
  padding:0;
@@ -47,89 +74,128 @@
47
  background:url("../images/loading.gif") center center no-repeat;
48
  text-indent:-9999px;
49
  }
50
- #hoverNav{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  position: absolute;
52
  top: 0;
53
  left: 0;
54
  height: 100%;
55
  width: 100%;
56
  z-index: 10;
57
- }
58
- #imageContainer>#hoverNav{ left: 0;}
59
- #hoverNav a{ outline: none;}
 
 
 
60
 
61
- #prevLinkImg, #nextLinkImg{
62
- width: 49%;
63
- height: 100%;
 
64
  background: transparent url("../images/blank.gif") no-repeat; /* Trick IE into showing hover */
65
  display: block;
66
- text-indent:-9999px;
67
- }
68
- #prevLinkImg { left: 0; float: left;}
69
- #nextLinkImg { right: 0; float: right;}
70
- #prevLinkImg:hover, #prevLinkImg:visited:hover { background: url("../images/prevlabel.gif") left 15% no-repeat; }
71
- #nextLinkImg:hover, #nextLinkImg:visited:hover { background: url("../images/nextlabel.gif") right 15% no-repeat; }
72
 
 
 
 
73
 
74
- #imageDataContainer{
75
- font: 10px Verdana, Helvetica, sans-serif;
76
- background-color: #fff;
77
- margin: 0 auto;
78
- line-height: 1.4em;
79
- }
80
-
81
- #imageData{
82
- padding:0 10px;
83
- }
84
- #imageDetails{ width: 70%; float: left; text-align: left; }
85
- #caption{ font-weight: bold; }
86
- #numberDisplay{ display: block; clear: left; }
87
- #detailsNav{ display: block; clear: left; padding:0 0 10px 0; }
88
- #prevLinkDetails { margin:0 8px 0 0; }
89
- #nextLinkDetails { margin:0 8px 0 0; }
90
- #closeLink {
91
  display:block;
92
  margin:0;
93
  padding:0 0 10px 0;
94
  text-decoration:none;
95
- float:right;
96
- width:66px;
97
- height:28px;
98
- background:url("../images/closelabel.gif") no-repeat;
99
  text-indent:-9999px;
100
  overflow:hidden;
101
- }
102
-
103
- #overlay{
104
- position: absolute;
105
- top: 0;
106
- left: 0;
107
- z-index: 3090;
108
- width: 100%;
109
- height: 500px;
110
- background-color: #151410;
111
- filter:alpha(opacity=60);
112
- -moz-opacity: 0.6;
113
- opacity: 0.6;
114
- }
115
-
116
 
117
- .clearfix:after {
118
- content: ".";
119
- display: block;
120
- height: 0;
121
- clear: both;
122
- visibility: hidden;
123
- }
124
 
125
- * html>body .clearfix {
126
- display: inline-block;
127
- width: 100%;
128
- }
129
-
130
- * html .clearfix {
131
- /* Hides from IE-mac \*/
132
- height: 1%;
133
- /* End hide from IE-mac */
134
- }
135
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset */
2
+ #slb_viewer * {
3
+ visibility: visible;
4
+ margin: 0;
5
+ padding: 0;
6
+ line-height: 1.4em;
7
+ text-align: left;
8
+ vertical-align: baseline;
9
+ white-space: normal;
10
+ outline: none;
11
+ border: 0px;
12
+ background: none;
13
+ opacity: 1;
14
+ width: auto;
15
+ height: auto;
16
+ position: static;
17
+ float: none;
18
+ clear: none;
19
+ font-family: Verdana, Helvetica, sans-serif;
20
+ }
21
+
22
+ /* General */
23
+
24
+ #slb_viewer {
25
  position: absolute;
26
  top: 20px;
27
  left: 0;
28
  width: 100%;
29
+ z-index: 999999;
30
  text-align: center;
31
  line-height: 0;
32
  color:#151410;
33
+ }
34
 
35
+ #slb_viewer a,
36
+ #slb_viewer a:hover {
37
  border-bottom:none;
38
  color:#151410;
39
  text-decoration:underline;
40
  }
41
 
42
+ #slb_viewer a img {
43
+ border: none;
44
+ }
45
 
46
+ #slb_container {
47
  position: relative;
48
  background-color: #fff;
49
  width: 250px;
50
  height: 250px;
51
  margin: 0 auto;
52
+ }
53
 
54
+ #slb_content {
55
  padding: 10px;
56
+ }
57
 
58
+ #slb_loading {
59
  position: absolute;
60
  top: 40%;
61
  left: 0%;
63
  width: 100%;
64
  text-align: center;
65
  line-height: 0;
66
+ }
67
+
68
+ #slb_slbLoading {
69
  display:block;
70
  margin:0 auto;
71
  padding:0;
74
  background:url("../images/loading.gif") center center no-repeat;
75
  text-indent:-9999px;
76
  }
77
+
78
+ #slb_overlay {
79
+ position: fixed;
80
+ top: 0;
81
+ left: 0;
82
+ z-index: 99999;
83
+ width: 100%;
84
+ height: 500px;
85
+ background-color: #151410;
86
+ filter:alpha(opacity=60);
87
+ -moz-opacity: 0.6;
88
+ opacity: 0.6;
89
+ }
90
+
91
+ /* Navigation */
92
+
93
+ #slb_nav .slb_navPrev,
94
+ #slb_nav .slb_navNext {
95
+ margin:0 8px 0 0;
96
+ }
97
+
98
+ #slb_nav {
99
+ display: block;
100
+ clear: left;
101
+ padding:0 0 10px 0;
102
+ }
103
+
104
+ #slb_nav_hover {
105
  position: absolute;
106
  top: 0;
107
  left: 0;
108
  height: 100%;
109
  width: 100%;
110
  z-index: 10;
111
+ left: 0;
112
+ }
113
+
114
+ #slb_nav_hover a {
115
+ outline: none;
116
+ }
117
 
118
+ #slb_nav_hover .slb_nav {
119
+ min-width: 65px;
120
+ width: 46%;
121
+ height: 98%;
122
  background: transparent url("../images/blank.gif") no-repeat; /* Trick IE into showing hover */
123
  display: block;
124
+ text-indent: -9999px;
125
+ }
 
 
 
 
126
 
127
+ #slb_nav_hover .slb_navPrev {
128
+ left: 0; float: left;
129
+ }
130
 
131
+ #slb_nav_hover .slb_navNext {
132
+ right: 0; float: right;
133
+ margin: 0;
134
+ }
135
+
136
+ #slb_nav_hover .slb_navPrev:hover,
137
+ #slb_nav_hover .slb_navPrev:visited:hover {
138
+ background: url("../images/prevlabel.gif") left 15% no-repeat;
139
+ }
140
+
141
+ #slb_nav_hover .slb_navNext:hover,
142
+ #slb_nav_hover .slb_navNext:visited:hover {
143
+ background: url("../images/nextlabel.gif") right 15% no-repeat;
144
+ }
145
+
146
+ .slb_slbClose {
 
147
  display:block;
148
  margin:0;
149
  padding:0 0 10px 0;
150
  text-decoration:none;
 
 
 
 
151
  text-indent:-9999px;
152
  overflow:hidden;
153
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ #slb_close {
156
+ float: right;
157
+ }
 
 
 
 
158
 
159
+ #slb_close,
160
+ #slb_close .slb_slbClose {
161
+ height:28px;
162
+ width:66px;
163
+ }
164
+
165
+ #slb_close .slb_slbClose {
166
+ background:url("../images/closelabel.gif") no-repeat;
167
+ }
168
+
169
+ /* Content */
170
+
171
+ #slb_data {
172
+ padding: 0 10px;
173
+ }
174
+
175
+ #slb_data_content {
176
+ width: 70%;
177
+ float: left;
178
+ }
179
+
180
+ .slb_dataCaption {
181
+ font-weight: bold;
182
+ }
183
+
184
+ #slb_data_desc .slb_dataDescription {
185
+ display: block;
186
+ padding: .5em 0;
187
+ }
188
+
189
+ .slb_dataNumber {
190
+ display: block;
191
+ clear: left;
192
+ }
193
+
194
+ #slb_details {
195
+ font: 10px Verdana, Helvetica, sans-serif;
196
+ background-color: #fff;
197
+ margin: 0 auto;
198
+ line-height: 1.4em;
199
+ text-align: left;
200
+ overflow: hidden;
201
+ }
images/closelabel_black.gif ADDED
Binary file
images/loading_black.gif ADDED
Binary file
images/nextlabel_black.gif ADDED
Binary file
images/prevlabel_black.gif ADDED
Binary file
includes/class.base.php CHANGED
@@ -11,10 +11,37 @@ require_once 'class.utilities.php';
11
  class SLB_Base {
12
 
13
  /**
14
- * Prefix for Cornerstone-related data (attributes, DB tables, etc.)
 
 
 
 
 
 
15
  * @var string
16
  */
17
  var $prefix = 'slb';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  /**
20
  * Utilities
@@ -33,140 +60,200 @@ class SLB_Base {
33
  * Constructor
34
  */
35
  function __construct() {
36
- $this->util =& new SLB_Utilities();
37
  }
38
 
 
 
39
  /**
40
  * Default initialization method
41
- * To be overriden by child classes
42
  */
43
- function init() {}
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- /**
46
- * Returns callback to instance method
47
- * @param string $method Method name
48
- * @return array Callback array
49
- */
50
- function &m($method) {
51
- return $this->util->m($this, $method);
 
 
52
  }
53
 
54
  /**
55
- * Retrieves post metadata for internal methods
56
- * Metadata set internally is wrapped in an array so it is unwrapped before returned the retrieved value
57
- * @see get_post_meta()
58
- * @param int $post_id Post ID
59
- * @param string $key Name of metadata to retrieve
60
- * @param boolean $single Whether or not to retrieve single value or not
61
- * @return mixed Retrieved post metadata
62
  */
63
- function post_meta_get($post_id, $key, $single = false) {
64
- $meta_value = get_post_meta($post_id, $this->post_meta_get_key($key), $single);
65
- if (is_array($meta_value) && count($meta_value) == 1)
66
- $meta_value = $meta_value[0];
67
- return $meta_value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
  /**
71
- * Wraps metadata in array for storage in database
72
- * @param mixed $meta_value Value to be set as metadata
73
- * @return array Wrapped metadata value
74
  */
75
- function post_meta_prepare_value($meta_value) {
76
- return array($meta_value);
 
 
 
 
 
 
 
 
 
 
 
 
77
  }
78
 
79
  /**
80
- * Adds Metadata for a post to database
81
- * For internal methods
82
- * @see add_post_meta
83
- * @param $post_id
84
- * @param $meta_key
85
- * @param $meta_value
86
- * @param $unique
87
- * @return boolean Result of operation
88
  */
89
- function post_meta_add($post_id, $meta_key, $meta_value, $unique = false) {
90
- $meta_value = $this->post_meta_value_prepare($meta_value);
91
- return add_post_meta($post_id, $meta_key, $meta_value, $unique);
 
 
92
  }
93
 
 
 
94
  /**
95
- * Updates post metadata for internal data/methods
96
- * @see update_post_meta()
97
- * @param $post_id
98
- * @param $meta_key
99
- * @param $meta_value
100
- * @param $prev_value
101
- * @return boolean Result of operation
102
  */
103
- function post_meta_update($post_id, $meta_key, $meta_value, $prev_value = '') {
104
- $meta_value = $this->post_meta_prepare_value($meta_value);
105
- return update_post_meta($post_id, $meta_key, $meta_value, $prev_value);
 
 
106
  }
107
 
 
 
108
  /**
109
- * Builds postmeta key for custom data set by plugin
110
- * @param string $key Base key name
111
- * @return string Formatted postmeta key
112
  */
113
- function post_meta_get_key($key) {
114
- $sep = '_';
115
- if ( strpos($key, $sep . $this->prefix) !== 0 ) {
116
- $key_base = func_get_args();
117
- if ( !empty($key_base) ) {
118
- $key = array_merge((array)$this->prefix, $key_base);
119
- return $sep . implode($sep, $key);
120
- }
121
- }
122
-
123
- return $key;
124
  }
125
 
 
 
126
  /**
127
  * Retrieve class prefix (with separator if set)
128
  * @param bool|string $sep Separator to append to class prefix (Default: no separator)
129
  * @return string Class prefix
130
  */
131
- function get_prefix($sep = false) {
132
- $sep = ( is_string($sep) ) ? $sep : '';
133
- $prefix = ( !empty($this->prefix) ) ? $this->prefix . $sep : '';
134
- return $prefix;
 
 
 
 
 
 
 
 
 
135
  }
136
 
137
  /**
138
  * Prepend plugin prefix to some text
139
  * @param string $text Text to add to prefix
140
- * @param string $sep Text used to separate prefix and text
 
141
  * @return string Text with prefix prepended
142
  */
143
- function add_prefix($text = '', $sep = '_') {
144
- return $this->get_prefix($sep) . $text;
145
- }
146
-
147
- function remove_prefix($text = '', $sep = '_') {
148
- if ( !empty($text) && strpos($text, ( $p = $this->get_prefix($sep) )) === 0 )
149
- $text = substr($text, strlen($p));
150
- return $text;
151
  }
152
 
153
  /**
154
- * Creates a meta key for storing post meta data
155
- * Prefixes standard prefixed text with underscore to hide meta data on post edit forms
156
- * @param string $text Text to use as base of meta key
157
- * @return string Formatted meta key
 
 
 
158
  */
159
- function make_meta_key($text = '') {
160
- return '_' . $this->add_prefix($text);
 
 
161
  }
162
 
163
  /**
164
- * Returns Database prefix for Cornerstone-related DB Tables
165
- * @return string Database prefix
 
166
  */
167
- function get_db_prefix() {
168
- global $wpdb;
169
- return $wpdb->prefix . $this->get_prefix('_');
170
  }
171
  }
172
 
11
  class SLB_Base {
12
 
13
  /**
14
+ * Variable name of base object in global scope
15
+ * @var string
16
+ */
17
+ var $base = 'slb';
18
+
19
+ /**
20
+ * Prefix for plugin-related data (attributes, DB tables, etc.)
21
  * @var string
22
  */
23
  var $prefix = 'slb';
24
+
25
+ /**
26
+ * Client files
27
+ * @var array
28
+ * Structure
29
+ * > Key: unique file ID
30
+ * > Properties
31
+ * > file (string) File path (Relative to plugin base)
32
+ * > deps (array) Script dependencies
33
+ * > Internal dependencies are wrapped in square brackets ([])
34
+ * > context (string|array)
35
+ * > Context in which the script should be included
36
+ * > in_footer (bool) optional [Default: FALSE]
37
+ * > If TRUE, file will be included in footer of page, otherwise it will be included in the header
38
+ *
39
+ * Array is processed and converted to an object on init
40
+ */
41
+ var $client_files = array(
42
+ 'scripts' => array(),
43
+ 'styles' => array()
44
+ );
45
 
46
  /**
47
  * Utilities
60
  * Constructor
61
  */
62
  function __construct() {
63
+ $this->util = new SLB_Utilities($this);
64
  }
65
 
66
+ /*-** Init **-*/
67
+
68
  /**
69
  * Default initialization method
70
+ * To be overridden by child classes
71
  */
72
+ function init() {
73
+ if ( !isset($this) )
74
+ return false;
75
+
76
+ /* Client files */
77
+ $this->init_client_files();
78
+
79
+ /* Hook */
80
+ $this->register_hooks();
81
+
82
+ /* Environment */
83
+ add_action('init', $this->m('init_env'), 1);
84
+ }
85
 
86
+ function register_hooks() {
87
+ //Activation
88
+ $func_activate = 'activate';
89
+ if ( method_exists($this, $func_activate) )
90
+ register_activation_hook($this->util->get_plugin_base_file(), $this->m($func_activate));
91
+ //Deactivation
92
+ $func_deactivate = 'deactivate';
93
+ if ( method_exists($this, $func_deactivate) )
94
+ register_deactivation_hook($this->util->get_plugin_base_file(), $this->m($func_deactivate));
95
  }
96
 
97
  /**
98
+ * Initialize environment (Localization, etc.)
99
+ * To be overriden by child class
100
+ * @uses `init` Action hook as trigger
 
 
 
 
101
  */
102
+ function init_env() {}
103
+
104
+ function init_client_files() {
105
+ foreach ( $this->client_files as $key => $val ) {
106
+ if ( empty($val) && isset($this->{$key}) )
107
+ $this->client_files[$key] =& $this->{$key};
108
+ $g =& $this->client_files[$key];
109
+ if ( is_array($g) && !empty($g) ) {
110
+ $g = $this->util->parse_client_files($g, $key);
111
+ }
112
+ }
113
+
114
+ //Register
115
+ add_action('init', $this->m('register_client_files'));
116
+
117
+ //Enqueue
118
+ $hook_enqueue = ( ( is_admin() ) ? 'admin' : 'wp' ) . '_enqueue_scripts' ;
119
+ add_action($hook_enqueue, $this->m('enqueue_client_files'));
120
+ }
121
+
122
+ function register_client_files() {
123
+ //Scripts
124
+ foreach ( $this->client_files as $type => $files ) {
125
+ if ( !empty($files) ) {
126
+ $func = $this->get_client_files_handler($type, 'register');
127
+ if ( !$func )
128
+ continue;
129
+ foreach ( $files as $f ) {
130
+ $params = array($f->id, $this->util->get_file_url($f->file), $f->deps, $this->util->get_plugin_version());
131
+ switch ( $type ) {
132
+ case 'scripts':
133
+ $params[] = $f->in_footer;
134
+ break;
135
+ case 'styles':
136
+ $params[] = $f->media;
137
+ break;
138
+ }
139
+ call_user_func_array($func, $params);
140
+ }
141
+ }
142
+ }
143
  }
144
 
145
  /**
146
+ * Enqueues files for client output (scripts/styles)
147
+ * Called by appropriate `enqueue_scripts` hook depending on context (admin or frontend)
148
+ * @return void
149
  */
150
+ function enqueue_client_files() {
151
+ //Enqueue files
152
+ foreach ( $this->client_files as $type => $files ) {
153
+ if ( !empty($files) ) {
154
+ $func = $this->get_client_files_handler($type, 'enqueue');
155
+ if ( !$func )
156
+ continue;
157
+ foreach ( $files as $f ) {
158
+ if ( empty($f->context) || $this->util->is_context($f->context) ) {
159
+ $func($f->id);
160
+ }
161
+ }
162
+ }
163
+ }
164
  }
165
 
166
  /**
167
+ * Build function name for handling client operations
 
 
 
 
 
 
 
168
  */
169
+ function get_client_files_handler($type, $action) {
170
+ $func = 'wp_' . $action . '_' . substr($type, 0, -1);
171
+ if ( !function_exists($func) )
172
+ $func = false;
173
+ return $func;
174
  }
175
 
176
+ /*-** Reflection **-*/
177
+
178
  /**
179
+ * Retrieve base object
180
+ * @return object|bool Base object (FALSE if object does not exist)
 
 
 
 
 
181
  */
182
+ function &get_base() {
183
+ $base = false;
184
+ if ( isset($GLOBALS[$this->base]) )
185
+ $base =& $GLOBALS[$this->base];
186
+ return $base;
187
  }
188
 
189
+ /*-** Method/Function calling **-*/
190
+
191
  /**
192
+ * Returns callback to instance method
193
+ * @param string $method Method name
194
+ * @return array Callback array
195
  */
196
+ function &m($method) {
197
+ return $this->util->m($this, $method);
 
 
 
 
 
 
 
 
 
198
  }
199
 
200
+ /*-** Prefix **-*/
201
+
202
  /**
203
  * Retrieve class prefix (with separator if set)
204
  * @param bool|string $sep Separator to append to class prefix (Default: no separator)
205
  * @return string Class prefix
206
  */
207
+ function get_prefix($sep = null) {
208
+ $args = func_get_args();
209
+ return call_user_func_array($this->util->m($this->util, 'get_prefix'), $args);
210
+ }
211
+
212
+ /**
213
+ * Check if a string is prefixed
214
+ * @param string $text Text to check for prefix
215
+ * @param string $sep (optional) Separator used
216
+ */
217
+ function has_prefix($text, $sep = null) {
218
+ $args = func_get_args();
219
+ return call_user_func_array($this->util->m($this->util, 'has_prefix'), $args);
220
  }
221
 
222
  /**
223
  * Prepend plugin prefix to some text
224
  * @param string $text Text to add to prefix
225
+ * @param string $sep (optional) Text used to separate prefix and text
226
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
227
  * @return string Text with prefix prepended
228
  */
229
+ function add_prefix($text, $sep = null, $once = true) {
230
+ $args = func_get_args();
231
+ return call_user_func_array($this->util->m($this->util, 'add_prefix'), $args);
 
 
 
 
 
232
  }
233
 
234
  /**
235
+ * Add prefix to variable reference
236
+ * Updates actual variable rather than return value
237
+ * @uses SLB_Utilities::add_prefix_ref();
238
+ * @param string $var Variable to add prefix to
239
+ * @param string $sep (optional) Separator text
240
+ * @param bool $once (optional) Add prefix only once
241
+ * @return void
242
  */
243
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
244
+ $args = func_get_args();
245
+ $args[0] =& $var;
246
+ call_user_func_array($this->util->m($this->util, 'add_prefix_ref'), $args);
247
  }
248
 
249
  /**
250
+ * Remove prefix from specified string
251
+ * @param string $text String to remove prefix from
252
+ * @param string $sep (optional) Separator used with prefix
253
  */
254
+ function remove_prefix($text, $sep = null) {
255
+ $args = func_get_args();
256
+ return call_user_func_array($this->util->m($this->util, 'remove_prefix'), $args);
257
  }
258
  }
259
 
includes/class.fields.php ADDED
@@ -0,0 +1,2251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'class.base.php';
4
+
5
+ /**
6
+ * Fields - Base class
7
+ * Core properties/methods for Content Type derivative classes
8
+ * @package Simple Lightbox
9
+ * @subpackage Fields
10
+ * @author SM
11
+ */
12
+ class SLB_Field_Base extends SLB_Base {
13
+ /**
14
+ * @var string Unique name
15
+ */
16
+ var $id = '';
17
+
18
+ /**
19
+ * ID formatting options
20
+ * Merged with defaults during initialization
21
+ * @see $id_formats_default
22
+ * @var array
23
+ */
24
+ var $id_formats = null;
25
+
26
+ /**
27
+ * Default ID Formatting options
28
+ * Structure:
29
+ * > Key (string): Format name
30
+ * > Val (array): Options
31
+ * @var array
32
+ */
33
+ var $id_formats_default = array(
34
+ 'attr_id' => array(
35
+ 'wrap' => array('open' => '_', 'segment_open' => '_'),
36
+ 'prefix' => array('get_container', 'get_id', 'add_prefix'),
37
+ 'recursive' => true
38
+ ),
39
+ 'attr_name' => array(
40
+ 'wrap' => array('open' => '[', 'close' => ']', 'segment_open' => '[', 'segment_close' => ']'),
41
+ 'recursive' => true,
42
+ 'prefix' => array('get_container', 'get_id', 'add_prefix')
43
+ )
44
+ );
45
+
46
+ /**
47
+ * Reference to parent object that current instance inherits from
48
+ * @var object
49
+ */
50
+ var $parent = null;
51
+
52
+ /**
53
+ * Title
54
+ * @var string
55
+ */
56
+ var $title = '';
57
+
58
+ /**
59
+ * @var string Short description
60
+ */
61
+ var $description = '';
62
+
63
+ /**
64
+ * @var array Object Properties
65
+ */
66
+ var $properties = array();
67
+
68
+ /**
69
+ * Initialization properties
70
+ * @var array
71
+ */
72
+ var $properties_init = null;
73
+
74
+ /**
75
+ * Structure: Property names stored as keys in group
76
+ * Root
77
+ * -> Group Name
78
+ * -> Property Name => Null
79
+ * Reason: Faster searching over large arrays
80
+ * @var array Groupings of Properties
81
+ */
82
+ var $property_groups = array();
83
+
84
+ /**
85
+ * Keys to filter out of properties array before setting properties
86
+ * @var array
87
+ */
88
+ var $property_filter = array('group');
89
+
90
+ /**
91
+ * Data for object
92
+ * May also contain data for nested objects
93
+ * @var mixed
94
+ */
95
+ var $data = null;
96
+
97
+ /**
98
+ * Whether data has been fetched or not
99
+ * @var bool
100
+ */
101
+ var $data_fetched = false;
102
+
103
+ /**
104
+ * @var array Script resources to include for object
105
+ */
106
+ var $scripts = array();
107
+
108
+ /**
109
+ * @var array CSS style resources to include for object
110
+ */
111
+ var $styles = array();
112
+
113
+ /**
114
+ * Hooks (Filters/Actions) for object
115
+ * @var array
116
+ */
117
+ var $hooks = array();
118
+
119
+ /**
120
+ * Mapping of child properties to parent members
121
+ * Allows more flexibility when creating new instances of child objects using property arrays
122
+ * Associative array structure:
123
+ * > Key: Child property to map FROM
124
+ * > Val: Parent property to map TO
125
+ * @var array
126
+ */
127
+ var $map = null;
128
+
129
+ /**
130
+ * Legacy Constructor
131
+ */
132
+ function SLB_Field_Base($id = '', $parent = null) {
133
+ $args = func_get_args();
134
+ call_user_func_array(array(&$this, '__construct'), $args);
135
+ }
136
+
137
+ /**
138
+ * Constructor
139
+ */
140
+ function __construct($id = '', $parent = null) {
141
+ parent::__construct();
142
+ //Normalize Properties
143
+ $args = func_get_args();
144
+ if ( func_num_args() > 1 && empty($parent) ) {
145
+ unset($args[1]);
146
+ $args = array_values($args);
147
+ }
148
+ $properties = $this->make_properties($this->util->func_get_options($args), array('id' => $id, 'parent' => $parent));
149
+ //Remove empty variables
150
+ if ( empty($properties['parent']) )
151
+ unset($properties['parent']);
152
+ //Save init properties
153
+ $this->properties_init = $properties;
154
+ //Set Properties
155
+ $this->set_properties($properties);
156
+ }
157
+
158
+ /* Getters/Setters */
159
+
160
+ /**
161
+ * Checks if the specified path exists in the object
162
+ * @param array $path Path to check for
163
+ * @return bool TRUE if path exists in object, FALSE otherwise
164
+ */
165
+ function path_isset($path = '') {
166
+ //Stop execution if no path is supplied
167
+ if ( empty($path) )
168
+ return false;
169
+ $args = func_get_args();
170
+ $path = $this->util->build_path($args);
171
+ $item =& $this;
172
+ //Iterate over path and check if each level exists before moving on to the next
173
+ for ($x = 0; $x < count($path); $x++) {
174
+ if ( $this->util->property_exists($item, $path[$x]) ) {
175
+ //Set $item as reference to next level in path for next iteration
176
+ $item =& $this->util->get_property($item, $path[$x]);
177
+ //$item =& $item[ $path[$x] ];
178
+ } else {
179
+ return false;
180
+ }
181
+ }
182
+ return true;
183
+ }
184
+
185
+ /**
186
+ * Retrieves a value from object using a specified path
187
+ * Checks to make sure path exists in object before retrieving value
188
+ * @param array $path Path to retrieve value from. Each item in array is a deeper dimension
189
+ * @return mixed Value at specified path
190
+ */
191
+ function &get_path_value($path = '') {
192
+ $ret = '';
193
+ $path = $this->util->build_path(func_get_args());
194
+ if ( $this->path_isset($path) ) {
195
+ $ret =& $this;
196
+ for ($x = 0; $x < count($path); $x++) {
197
+ if ( 0 == $x )
198
+ $ret =& $ret->{ $path[$x] };
199
+ else
200
+ $ret =& $ret[ $path[$x] ];
201
+ }
202
+ }
203
+ return $ret;
204
+ }
205
+
206
+ /**
207
+ * Search for specified member value in field type ancestors
208
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
209
+ * @param string $name Value to retrieve from member
210
+ * @return mixed Member value if found (Default: empty string)
211
+ */
212
+ function get_parent_value($member, $name = '', $default = '') {
213
+ $parent =& $this->get_parent();
214
+ return $this->get_object_value($parent, $member, $name, $default, 'parent');
215
+ }
216
+
217
+ /**
218
+ * Retrieves specified member value
219
+ * Handles inherited values
220
+ * Merging corresponding parents if value is an array (e.g. for property groups)
221
+ * @param string|array $member Member to search. May also contain a path to the desired member
222
+ * @param string $name Value to retrieve from member
223
+ * @param mixed $default Default value if no value found (Default: empty string)
224
+ * @param string $dir Direction to move through hierarchy to find value
225
+ * Possible Values:
226
+ * parent (default) - Search through field parents
227
+ * current - Do not search through connected objects
228
+ * container - Search through field containers
229
+ * caller - Search through field callers
230
+ * @return mixed Specified member value
231
+ * @todo Return reference
232
+ */
233
+ function &get_member_value($member, $name = '', $default = '', $dir = 'parent') {
234
+ //Check if path to member is supplied
235
+ $path = array();
236
+ if ( is_array($member) && isset($member['tag']) ) {
237
+ if ( isset($member['attributes']['ref_base']) ) {
238
+ if ( 'root' != $member['attributes']['ref_base'] )
239
+ $path[] = $member['attributes']['ref_base'];
240
+ } else {
241
+ $path[] = 'properties';
242
+ }
243
+
244
+ $path[] = $member['tag'];
245
+ } else {
246
+ $path = $member;
247
+ }
248
+
249
+ $path = $this->util->build_path($path, $name);
250
+ //Set defaults and prepare data
251
+ $val = $default;
252
+ $inherit = false;
253
+ $inherit_tag = '{inherit}';
254
+
255
+ /* Determine whether the value must be retrieved from a parent/container object
256
+ * Conditions:
257
+ * > Path does not exist in current field
258
+ * > Path exists and is not an object, but at least one of the following is true:
259
+ * > Value at path is an array (e.g. properties, elements, etc. array)
260
+ * > Parent/container values should be merged with retrieved array
261
+ * > Value at path is a string that inherits from another field
262
+ * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value
263
+ */
264
+
265
+ $deeper = false;
266
+
267
+ if ( !$this->path_isset($path) )
268
+ $deeper = true;
269
+ else {
270
+ $val = $this->get_path_value($path);
271
+ if ( !is_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) )
272
+ $deeper = true;
273
+ else
274
+ $deeper = false;
275
+ }
276
+ if ( $deeper && 'current' != $dir ) {
277
+ //Get Parent value (recursive)
278
+ $ex_val = ( 'parent' != $dir ) ? $this->get_container_value($member, $name, $default) : $this->get_parent_value($member, $name, $default);
279
+ //Handle inheritance
280
+ if ( is_array($val) ) {
281
+ //Combine Arrays
282
+ if ( is_array($ex_val) )
283
+ $val = array_merge($ex_val, $val);
284
+ } elseif ( $inherit !== false ) {
285
+ //Replace placeholder with inherited string
286
+ $val = str_replace($inherit_tag, $ex_val, $val);
287
+ } else {
288
+ //Default: Set parent value as value
289
+ $val = $ex_val;
290
+ }
291
+ }
292
+
293
+ return $val;
294
+ }
295
+
296
+ /**
297
+ * Search for specified member value in an object
298
+ * @param object $object Reference to object to retrieve value from
299
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
300
+ * @param string $name (optional) Value to retrieve from member
301
+ * @param mixed $default (optional) Default value to use if no value found (Default: empty string)
302
+ * @param string $dir Direction to move through hierarchy to find value @see SLB_Field_Type::get_member_value() for possible values
303
+ * @return mixed Member value if found (Default: $default)
304
+ */
305
+ function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') {
306
+ $ret = $default;
307
+ if ( is_object($object) && method_exists($object, 'get_member_value') )
308
+ $ret = $object->get_member_value($member, $name, $default, $dir);
309
+ return $ret;
310
+ }
311
+
312
+ /**
313
+ * Set item ID
314
+ * @param string $id Unique item ID
315
+ */
316
+ function set_id($id) {
317
+ if ( empty($id) || !is_string($id) )
318
+ return false;
319
+ $this->id = trim($id);
320
+ }
321
+
322
+ /**
323
+ * Retrieves field ID
324
+ * @param array|string $options (optional) Options or ID of format to use
325
+ * @return string item ID
326
+ */
327
+ function get_id($options = array()) {
328
+ $item_id = trim($this->id);
329
+
330
+ $formats = $this->get_id_formats();
331
+
332
+ //Setup options
333
+ $wrap_default = array('open' => '', 'close' => '', 'segment_open' => '', 'segment_close' => '');
334
+
335
+ $options_default = array(
336
+ 'format' => null,
337
+ 'wrap' => array(),
338
+ 'segments_pre' => null,
339
+ 'prefix' => '',
340
+ 'recursive' => false
341
+ );
342
+
343
+ //Load options based on format
344
+ if ( !is_array($options) )
345
+ $options = array('format' => $options);
346
+ if ( isset($options['format']) && is_string($options['format']) && isset($formats[$options['format']]) )
347
+ $options_default = wp_parse_args($formats[$options['format']], $options_default);
348
+ else
349
+ unset($options['format']);
350
+ $options = wp_parse_args($options, $options_default);
351
+ //Import options into function
352
+ extract($options);
353
+
354
+ //Validate options
355
+ $wrap = wp_parse_args($wrap, $wrap_default);
356
+
357
+ if ( !is_array($segments_pre) )
358
+ $segments_pre = array($segments_pre);
359
+ $segments_pre = array_reverse($segments_pre);
360
+
361
+ //Format ID based on options
362
+
363
+ $item_id = array($item_id);
364
+
365
+ //Add parent objects to ID
366
+ if ( !!$recursive ) {
367
+ //Create array of ID components
368
+ $c = $this->get_caller();
369
+ while ( !!$c ) {
370
+ //Add ID of current caller to array
371
+ if ( method_exists($c, 'get_id') && ( $itemp = $c->get_id() ) && !empty($itemp) )
372
+ $item_id = $itemp;
373
+ //Get parent object
374
+ $c = ( method_exists($c, 'get_caller') ) ? $c->get_caller() : null;
375
+ $itemp = '';
376
+ }
377
+ unset($c);
378
+ }
379
+
380
+ //Additional segments (Pre)
381
+ foreach ( $segments_pre as $seg ) {
382
+ if ( is_null($seg) )
383
+ continue;
384
+ if ( is_object($seg) )
385
+ $seg = (array)$seg;
386
+ if ( is_array($seg) )
387
+ $item_id = array_merge($item_id, array_reverse($seg));
388
+ elseif ( '' != strval($seg) )
389
+ $item_id[] = strval($seg);
390
+ }
391
+
392
+ //Prefix
393
+ if ( is_array($prefix) ) {
394
+ //Array is sequence of instance methods to call on object
395
+ //Last array member can be an array of parameters to pass to methods
396
+ $count = count($prefix);
397
+ $args = ( $count > 1 && is_array($prefix[$count - 1]) ) ? array_pop($prefix) : array();
398
+ $p = $this;
399
+ $val = '';
400
+ //Iterate through methods
401
+ foreach ( $prefix as $m ) {
402
+ //Build callback
403
+ $m = $this->util->m($p, $m);
404
+ //Call callback
405
+ if ( is_callable($m) )
406
+ $val = call_user_func_array($m, $args);
407
+ //Process returned value
408
+ if ( is_object($val) )
409
+ $p = $val; //Use returned object in next round
410
+ else
411
+ array_unshift($args, $val); //Pass returned value as parameter to next method on using current object
412
+ }
413
+ $prefix = $val;
414
+ unset($p, $val);
415
+ }
416
+ if ( is_numeric($prefix) )
417
+ $prefix = strval($prefix);
418
+ if ( empty($prefix) || !is_string($prefix) )
419
+ $prefix = '';
420
+
421
+ //Convert array to string
422
+ $item_id = $prefix . $wrap['open'] . implode($wrap['segment_close'] . $wrap['segment_open'], array_reverse($item_id)) . $wrap['close'];
423
+ return $item_id;
424
+ }
425
+
426
+ /**
427
+ * Retrieve ID formatting options for class
428
+ * Format options arrays are merged together and saved to $id_formats
429
+ * @uses $id_formats
430
+ * @uses $id_formats_default
431
+ * @return array ID Formatting options
432
+ */
433
+ function &get_id_formats() {
434
+ if ( is_null($this->id_formats) ) {
435
+ $this->id_formats = wp_parse_args($this->id_formats, $this->id_formats_default);
436
+ }
437
+ return $this->id_formats;
438
+ }
439
+
440
+ /**
441
+ * Retrieve value from data member
442
+ * @param string $context Context to format data for
443
+ * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE)
444
+ * @return mixed Value at specified path
445
+ */
446
+ function get_data($context = '', $top = true) {
447
+ $opt_d = array('context' => '', 'top' => true);
448
+ $args = func_get_args();
449
+ $a = false;
450
+ if ( count($args) == 1 && is_array($args[0]) && !empty($args[0]) ) {
451
+ $a = true;
452
+ $args = wp_parse_args($args[0], $opt_d);
453
+ extract($args);
454
+ }
455
+
456
+ if ( is_string($top) ) {
457
+ if ( 'false' == $top )
458
+ $top = false;
459
+ elseif ( 'true' == $top )
460
+ $top = true;
461
+ elseif ( is_numeric($top) )
462
+ $top = intval($top);
463
+ }
464
+ $top = !!$top;
465
+ $obj =& $this;
466
+ $obj_path = array(&$this);
467
+ $path = array();
468
+ if ( $top ) {
469
+ //Iterate through hiearchy to get top-most object
470
+ while ( !empty($obj) ) {
471
+ $new = null;
472
+ //Try to get caller first
473
+ if ( method_exists($obj, 'get_caller') ) {
474
+ $checked = true;
475
+ $new =& $obj->get_caller();
476
+ }
477
+ //Try to get container if no caller found
478
+ if ( empty($new) && method_exists($obj, 'get_container') ) {
479
+ $checked = true;
480
+ $new =& $obj->get_container();
481
+ //Load data
482
+ if ( method_exists($new, 'load_data') ) {
483
+ $new->load_data();
484
+ }
485
+ }
486
+
487
+ $obj =& $new;
488
+ unset($new);
489
+ //Stop iteration
490
+ if ( !empty($obj) ) {
491
+ //Add object to path if it is valid
492
+ $obj_path[] =& $obj;
493
+ }
494
+ }
495
+ unset($obj);
496
+ }
497
+
498
+ //Check each object (starting with top-most) for matching data for current field
499
+
500
+ //Reverse array
501
+ $obj_path = array_reverse($obj_path);
502
+ //Build path for data location
503
+ foreach ( $obj_path as $obj ) {
504
+ if ( method_exists($obj, 'get_id') )
505
+ $path[] = $obj->get_id();
506
+ }
507
+ //Iterate through objects
508
+ while ( !empty($obj_path) ) {
509
+ //Get next object
510
+ $obj =& array_shift($obj_path);
511
+ //Shorten path
512
+ array_shift($path);
513
+ //Check for value in object and stop iteration if matching data found
514
+ $val = $this->get_object_value($obj, 'data', $path, null, 'current');
515
+ if ( !is_null($val) ) {
516
+ break;
517
+ }
518
+ }
519
+ return $this->format($val, $context);
520
+ }
521
+
522
+ /**
523
+ * Sets value in data member
524
+ * Sets value to data member itself by default
525
+ * @param mixed $value Value to set
526
+ * @param string|array $name Name of value to set (Can also be path to value)
527
+ */
528
+ function set_data($value, $name = '') {
529
+ $ref =& $this->get_path_value('data', $name);
530
+ $ref = $value;
531
+ }
532
+
533
+ /**
534
+ * Sets parent object of current instance
535
+ * Parent objects must be the same object type as current instance
536
+ * @uses SLB to get field type definition
537
+ * @uses SLB_Fields::has() to check if field type exists
538
+ * @uses SLB_Fields::get() to retrieve field type object reference
539
+ * @param string|object $parent Parent ID or reference
540
+ */
541
+ function set_parent($parent = null) {
542
+ //Stop processing if parent empty
543
+ if ( empty($parent) && !is_string($this->parent) )
544
+ return false;
545
+ //Parent passed as object reference wrapped in array
546
+ if ( is_array($parent) && isset($parent[0]) && is_object($parent[0]) )
547
+ $parent =& $parent[0];
548
+
549
+ //No parent set but parent ID (previously) set in object
550
+ if ( empty($parent) && is_string($this->parent) )
551
+ $parent = $this->parent;
552
+
553
+ //Retrieve reference object if ID was supplied
554
+ if ( is_string($parent) ) {
555
+ $parent = trim($parent);
556
+ //Get parent object reference
557
+ /**
558
+ * @var SLB
559
+ */
560
+ $b =& $this->get_base();
561
+ if ( $b && isset($b->fields) && $b->fields->has($parent) ) {
562
+ $parent =& $b->fields->get($parent);
563
+ }
564
+ }
565
+
566
+ //Set parent value on object
567
+ if ( is_string($parent) || is_object($parent) )
568
+ $this->parent =& $parent;
569
+ }
570
+
571
+ /**
572
+ * Retrieve field type parent
573
+ * @return SLB_Field_Type Reference to parent field
574
+ */
575
+ function &get_parent() {
576
+ return $this->parent;
577
+ }
578
+
579
+ /**
580
+ * Set object title
581
+ * @param string $title Title for object
582
+ * @param string $plural Plural form of title
583
+ */
584
+ function set_title($title = '') {
585
+ $this->title = strip_tags(trim($title));
586
+ }
587
+
588
+ /**
589
+ * Retrieve object title
590
+ */
591
+ function get_title() {
592
+ return $this->get_member_value('title', '','', 'current');
593
+ }
594
+
595
+ /**
596
+ * Set object description
597
+ * @param string $description Description for object
598
+ */
599
+ function set_description($description = '') {
600
+ $this->description = strip_tags(trim($description));
601
+ }
602
+
603
+ /**
604
+ * Retrieve object description
605
+ * @return string Object description
606
+ */
607
+ function get_description() {
608
+ $dir = 'current';
609
+ return $this->get_member_value('description', '','', $dir);
610
+ return $desc;
611
+ }
612
+
613
+ /**
614
+ * Sets multiple properties on field type at once
615
+ * @param array $properties Properties. Each element is an array containing the arguments to set a new property
616
+ * @return boolean TRUE if successful, FALSE otherwise
617
+ */
618
+ function set_properties($properties) {
619
+ if ( !is_array($properties) )
620
+ return false;
621
+
622
+ //Set Member properties
623
+ foreach ( $properties as $prop => $val ) {
624
+ if ( ( $m = 'set_' . $prop ) && method_exists($this, $m) ) {
625
+ $this->{$m}($val);
626
+ //Remove member property from array
627
+ unset($properties[$prop]);
628
+ }
629
+ }
630
+
631
+ //Filter properties
632
+ $properties = $this->filter_properties($properties);
633
+
634
+ //Set additional instance properties
635
+ foreach ( $properties as $name => $val) {
636
+ $this->set_property($name, $val);
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Remap properties based on $map
642
+ * @uses $map For determine how child properties should map to parent properties
643
+ * @uses SLB_Utlities::array_remap() to perform array remapping
644
+ * @param array $properties Associative array of properties
645
+ * @return array Remapped properties
646
+ */
647
+ function remap_properties($properties) {
648
+ //Return remapped properties
649
+ return $this->util->array_remap($properties, $this->map);
650
+ }
651
+
652
+ /**
653
+ * Build properties array
654
+ * Accepts a variable number of additional arrays of default properties
655
+ * that will be merged in order from last to first
656
+ * (e.g. first array overwrites duplicate members in last)
657
+ * @uses SLB_Field_Base::remap_properties() to remap properties members if necessary
658
+ * @param array $props Instance properties
659
+ * @param array $defaults Default properties
660
+ * @return array Normalized properties
661
+ */
662
+ function make_properties($props, $defaults = array()) {
663
+ $args = func_get_args();
664
+ $args = array_reverse($args);
665
+ $props = array();
666
+ foreach ( $args as $arg ) {
667
+ $props = wp_parse_args($arg, $props);
668
+ }
669
+ return $this->remap_properties($props);
670
+ }
671
+
672
+ /**
673
+ * Filter property members
674
+ * @uses $property_filter to remove define members to remove from $properties
675
+ * @param array $props Properties
676
+ * @return array Filtered properties
677
+ */
678
+ function filter_properties($props = array()) {
679
+ return $this->util->array_filter_keys($props, $this->property_filter);
680
+ }
681
+
682
+ /**
683
+ * Add/Set a property on the field definition
684
+ * @param string $name Name of property
685
+ * @param mixed $value Default value for property
686
+ * @param string|array $group Group(s) property belongs to
687
+ * @return boolean TRUE if property is successfully added to field type, FALSE otherwise
688
+ */
689
+ function set_property($name, $value = '', $group = null) {
690
+ //Do not add if property name is not a string
691
+ if ( !is_string($name) )
692
+ return false;
693
+ //Create property array
694
+ $prop_arr = array();
695
+ $prop_arr['value'] = $value;
696
+ //Add to properties array
697
+ $this->properties[$name] = $value;
698
+ //Add property to specified groups
699
+ if ( !empty($group) ) {
700
+ $this->set_group_property($group, $name);
701
+ }
702
+ return true;
703
+ }
704
+
705
+ /**
706
+ * Retreives property from field type
707
+ * @param string $name Name of property to retrieve
708
+ * @return mixed Specified Property if exists (Default: Empty string)
709
+ */
710
+ function get_property($name) {
711
+ $val = $this->get_member_value('properties', $name);
712
+ return $val;
713
+ }
714
+
715
+ /**
716
+ * Removes a property from item
717
+ * @param string $name Property ID
718
+ */
719
+ function remove_property($name) {
720
+ //Remove property
721
+ if ( isset($this->properties[$name]) )
722
+ unset($this->properties[$name]);
723
+ //Remove from group
724
+ foreach ( array_keys($this->property_groups) as $g ) {
725
+ if ( isset($this->property_groups[$g][$name]) ) {
726
+ unset($this->property_groups[$g][$name]);
727
+ break;
728
+ }
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Adds Specified Property to a Group
734
+ * @param string|array $group Group(s) to add property to
735
+ * @param string $property Property to add to group
736
+ */
737
+ function set_group_property($group, $property) {
738
+ if ( is_string($group) && isset($this->property_groups[$group][$property]) )
739
+ return;
740
+ if ( !is_array($group) ) {
741
+ $group = array($group);
742
+ }
743
+
744
+ foreach ($group as $g) {
745
+ $g = trim($g);
746
+ //Initialize group if it doesn't already exist
747
+ if ( !isset($this->property_groups[$g]) )
748
+ $this->property_groups[$g] = array();
749
+
750
+ //Add property to group
751
+ $this->property_groups[$g][$property] = null;
752
+ }
753
+ }
754
+
755
+ /**
756
+ * Retrieve property group
757
+ * @param string $group Group to retrieve
758
+ * @return array Array of properties in specified group
759
+ */
760
+ function get_group($group) {
761
+ return $this->get_member_value('property_groups', $group, array());
762
+ }
763
+
764
+ /**
765
+ * Save field data
766
+ * Child classes will define their own
767
+ * functionality for this method
768
+ * @return bool TRUE if save was successful (FALSE otherwise)
769
+ */
770
+ function save() {
771
+ return true;
772
+ }
773
+
774
+ /*-** Hooks **-*/
775
+
776
+ /**
777
+ * Retrieve hooks added to object
778
+ * @return array Hooks
779
+ */
780
+ function get_hooks() {
781
+ return $this->get_member_value('hooks', '', array());
782
+ }
783
+
784
+ /**
785
+ * Add hook for object
786
+ * @see add_filter() for parameter defaults
787
+ * @param $tag
788
+ * @param $function_to_add
789
+ * @param $priority
790
+ * @param $accepted_args
791
+ */
792
+ function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
793
+ //Create new array for tag (if not already set)
794
+ if ( !isset($this->hooks[$tag]) )
795
+ $this->hooks[$tag] = array();
796
+ //Build Unique ID
797
+ if ( is_string($function_to_add) )
798
+ $id = $function_to_add;
799
+ elseif ( is_array($function_to_add) && !empty($function_to_add) )
800
+ $id = strval($function_to_add[count($function_to_add) - 1]);
801
+ else
802
+ $id = 'function_' . ( count($this->hooks[$tag]) + 1 );
803
+ //Add hook
804
+ $this->hooks[$tag][$id] = func_get_args();
805
+ }
806
+
807
+ /**
808
+ * Convenience method for adding an action for object
809
+ * @see add_filter() for parameter defaults
810
+ * @param $tag
811
+ * @param $function_to_add
812
+ * @param $priority
813
+ * @param $accepted_args
814
+ */
815
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
816
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
817
+ }
818
+
819
+ /**
820
+ * Convenience method for adding a filter for object
821
+ * @see add_filter() for parameter defaults
822
+ * @param $tag
823
+ * @param $function_to_add
824
+ * @param $priority
825
+ * @param $accepted_args
826
+ */
827
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
828
+ $this->add_hook($tag, $function_to_add, $priority, $accepted_args);
829
+ }
830
+
831
+ /*-** Dependencies **-*/
832
+
833
+ /**
834
+ * Adds dependency to object
835
+ * @param string $type Type of dependency to add (script, style)
836
+ * @param array|string $context When dependency will be added (@see SLB_Utilities::get_action() for possible contexts)
837
+ * @see wp_enqueue_script for the following of the parameters
838
+ * @param $handle
839
+ * @param $src
840
+ * @param $deps
841
+ * @param $ver
842
+ * @param $ex
843
+ */
844
+ function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) {
845
+ $args = func_get_args();
846
+ //Remove type/context from arguments
847
+ $args = array_slice($args, 2);
848
+
849
+ //Set context
850
+ if ( !is_array($context) ) {
851
+ //Wrap single contexts in an array
852
+ if ( is_string($context) )
853
+ $context = array($context);
854
+ else
855
+ $context = array();
856
+ }
857
+ //Add file to instance property
858
+ if ( isset($this->{$type}) && is_array($this->{$type}) )
859
+ $this->{$type}[$handle] = array('context' => $context, 'params' => $args);
860
+ }
861
+
862
+ /**
863
+ * Add script to object to be added in specified contexts
864
+ * @param array|string $context Array of contexts to add script to page
865
+ * @see wp_enqueue_script for the following of the parameters
866
+ * @param $handle
867
+ * @param $src
868
+ * @param $deps
869
+ * @param $ver
870
+ * @param $in_footer
871
+ */
872
+ function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
873
+ $args = func_get_args();
874
+ //Add file type to front of arguments array
875
+ array_unshift($args, 'scripts');
876
+ call_user_func_array($this->m('add_dependency'), $args);
877
+ }
878
+
879
+ /**
880
+ * Retrieve script dependencies for object
881
+ * @return array Script dependencies
882
+ */
883
+ function get_scripts() {
884
+ return $this->get_member_value('scripts', '', array());
885
+ }
886
+
887
+ /**
888
+ * Add style to object to be added in specified contexts
889
+ * @param array|string $context Array of contexts to add style to page
890
+ * @see wp_enqueue_style for the following of the parameters
891
+ * @param $handle
892
+ * @param $src
893
+ * @param $deps
894
+ * @param $ver
895
+ * @param $in_footer
896
+ */
897
+ function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) {
898
+ $args = func_get_args();
899
+ array_unshift($args, 'styles');
900
+ call_user_func_array($this->m('add_dependency'), $args);
901
+ }
902
+
903
+ /**
904
+ * Retrieve Style dependencies for object
905
+ * @return array Style dependencies
906
+ */
907
+ function get_styles() {
908
+ return $this->get_member_value('styles', '', array());
909
+ }
910
+
911
+ /* Helpers */
912
+
913
+ /**
914
+ * Format value based on specified context
915
+ * @param mixed $value Value to format
916
+ * @param string $context Current context
917
+ * @return mixed Formatted value
918
+ */
919
+ function format($value, $context = '') {
920
+ $handler = 'format_' . trim(strval($context));
921
+ //Only process if context is valid and has a handler
922
+ if ( !empty($context) && method_exists($this, $handler) ) {
923
+ //Pass value to handler
924
+ $value = $this->{$handler}($value, $context);
925
+ }
926
+ //Return formatted value
927
+ return $value;
928
+ }
929
+
930
+ /**
931
+ * Format value for output in form field
932
+ * @param mixed $value Value to format
933
+ * @return mixed Formatted value
934
+ */
935
+ function format_form($value) {
936
+ if ( is_string($value) )
937
+ $value = htmlspecialchars($value);
938
+ return $value;
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Field Types
944
+ * Stores properties for a specific field
945
+ * @package Simple Lightbox
946
+ * @subpackage Fields
947
+ * @author SM
948
+ */
949
+ class SLB_Field_Type extends SLB_Field_Base {
950
+ /* Properties */
951
+
952
+ /**
953
+ * @var array Array of Field types that make up current Field type
954
+ */
955
+ var $elements = array();
956
+
957
+ /**
958
+ * @var array Field type layouts
959
+ */
960
+ var $layout = array();
961
+
962
+ /**
963
+ * @var SLB_Field_Type Parent field type (reference)
964
+ */
965
+ var $parent = null;
966
+
967
+ /**
968
+ * Object that field is in
969
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
970
+ */
971
+ var $container = null;
972
+
973
+ /**
974
+ * Object that called field
975
+ * Used to determine field hierarchy/nesting
976
+ * @var SLB_Field|SLB_Field_Type|SLB_Field_Collection
977
+ */
978
+ var $caller = null;
979
+
980
+ /**
981
+ * Legacy Constructor
982
+ */
983
+ function SLB_Field_Type($id = '', $parent = null) {
984
+ $args = func_get_args();
985
+ call_user_func_array(array(&$this, '__construct'), $args);
986
+ }
987
+
988
+ function __construct($id = '', $parent = null) {
989
+ parent::__construct($id, $parent);
990
+ }
991
+
992
+ /* Getters/Setters */
993
+
994
+ /**
995
+ * Search for specified member value in field's container object (if exists)
996
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
997
+ * @param string $name Value to retrieve from member
998
+ * @return mixed Member value if found (Default: empty string)
999
+ */
1000
+ function get_container_value($member, $name = '', $default = '') {
1001
+ $container =& $this->get_container();
1002
+ return $this->get_object_value($container, $member, $name, $default, 'container');
1003
+ }
1004
+
1005
+ /**
1006
+ * Search for specified member value in field's container object (if exists)
1007
+ * @param string $member Name of object member to search (e.g. properties, layout, etc.)
1008
+ * @param string $name Value to retrieve from member
1009
+ * @return mixed Member value if found (Default: empty string)
1010
+ */
1011
+ function get_caller_value($member, $name = '', $default = '') {
1012
+ $caller =& $this->get_caller();
1013
+ return $this->get_object_value($caller, $member, $name, $default, 'caller');
1014
+ }
1015
+
1016
+ /**
1017
+ * Sets reference to container object of current field
1018
+ * Reference is cleared if no valid object is passed to method
1019
+ * @param object $container
1020
+ */
1021
+ function set_container(&$container) {
1022
+ if ( !empty($container) && is_object($container) ) {
1023
+ //Set as param as container for current field
1024
+ $this->container =& $container;
1025
+ } else {
1026
+ //Clear container member if argument is invalid
1027
+ $this->clear_container();
1028
+ }
1029
+ }
1030
+
1031
+ /**
1032
+ * Clears reference to container object of current field
1033
+ */
1034
+ function clear_container() {
1035
+ $this->container = null;
1036
+ }
1037
+
1038
+ /**
1039
+ * Retrieves reference to container object of current field
1040
+ * @return object Reference to container object
1041
+ */
1042
+ function &get_container() {
1043
+ $ret = null;
1044
+ if ( $this->has_container() )
1045
+ $ret =& $this->container;
1046
+ return $ret;
1047
+ }
1048
+
1049
+ /**
1050
+ * Checks if field has a container reference
1051
+ * @return bool TRUE if field is contained, FALSE otherwise
1052
+ */
1053
+ function has_container() {
1054
+ return !empty($this->container);
1055
+ }
1056
+
1057
+ /**
1058
+ * Sets reference to calling object of current field
1059
+ * Any existing reference is cleared if no valid object is passed to method
1060
+ * @param object $caller Calling object
1061
+ */
1062
+ function set_caller(&$caller) {
1063
+ if ( !empty($caller) && is_object($caller) )
1064
+ $this->caller =& $caller;
1065
+ else
1066
+ $this->clear_caller();
1067
+ }
1068
+
1069
+ /**
1070
+ * Clears reference to calling object of current field
1071
+ */
1072
+ function clear_caller() {
1073
+ unset($this->caller);
1074
+ }
1075
+
1076
+ /**
1077
+ * Retrieves reference to caller object of current field
1078
+ * @return object Reference to caller object
1079
+ */
1080
+ function &get_caller() {
1081
+ $ret = null;
1082
+ if ( $this->has_caller() )
1083
+ $ret =& $this->caller;
1084
+ return $ret;
1085
+ }
1086
+
1087
+ /**
1088
+ * Checks if field has a caller reference
1089
+ * @return bool TRUE if field is called by another field, FALSE otherwise
1090
+ */
1091
+ function has_caller() {
1092
+ return !empty($this->caller);
1093
+ }
1094
+
1095
+
1096
+
1097
+ /**
1098
+ * Sets an element for the field type
1099
+ * @param string $name Name of element
1100
+ * @param SLB_Field_Type $type Reference of field type to use for element
1101
+ * @param array $properties Properties for element (passed as keyed associative array)
1102
+ * @param string $id_prop Name of property to set $name to (e.g. ID, etc.)
1103
+ */
1104
+ function set_element($name, $type, $properties = array(), $id_prop = 'id') {
1105
+ $name = trim(strval($name));
1106
+ if ( empty($name) )
1107
+ return false;
1108
+ //Create new field for element
1109
+ $el = new SLB_Field($name, $type);
1110
+ //Set container to current field instance
1111
+ $el->set_container($this);
1112
+ //Add properties to element
1113
+ $el->set_properties($properties);
1114
+ //Save element to current instance
1115
+ $this->elements[$name] =& $el;
1116
+ }
1117
+
1118
+ /**
1119
+ * Add a layout to the field
1120
+ * @param string $name Name of layout
1121
+ * @param string $value Layout text
1122
+ */
1123
+ function set_layout($name, $value = '') {
1124
+ if ( !is_string($name) )
1125
+ return false;
1126
+ $name = trim($name);
1127
+ $this->layout[$name] = $value;
1128
+ return true;
1129
+ }
1130
+
1131
+ /**
1132
+ * Retrieve specified layout
1133
+ * @param string $name Layout name
1134
+ * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE)
1135
+ * @return string Specified layout text
1136
+ */
1137
+ function get_layout($name = 'form', $parse_nested = true) {
1138
+ //Retrieve specified layout (use $name value if no layout by that name exists)
1139
+ $layout = $this->get_member_value('layout', $name, $name);
1140
+
1141
+ //Find all nested layouts in current layout
1142
+ if ( !empty($layout) && !!$parse_nested ) {
1143
+ $ph = $this->get_placeholder_defaults();
1144
+
1145
+ while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) {
1146
+ //Iterate through the different types of layout placeholders
1147
+ foreach ($ph->match as $tag => $instances) {
1148
+ //Iterate through instances of a specific type of layout placeholder
1149
+ foreach ($instances as $instance) {
1150
+ //Get nested layout
1151
+ $nested_layout = $this->get_member_value($instance);
1152
+
1153
+ //Replace layout placeholder with retrieved item data
1154
+ if ( !empty($nested_layout) )
1155
+ $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout);
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ return $layout;
1162
+ }
1163
+
1164
+ /**
1165
+ * Checks if specified layout exists
1166
+ * Finds layout if it exists in current object or any of its parents
1167
+ * @param string $layout Name of layout to check for
1168
+ * @return bool TRUE if layout exists, FALSE otherwise
1169
+ */
1170
+ function has_layout($layout) {
1171
+ $ret = false;
1172
+ if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) {
1173
+ $layout = $this->get_member_value('layout', $layout, false);
1174
+ if ( $layout !== false )
1175
+ $ret = true;
1176
+ }
1177
+
1178
+ return $ret;
1179
+ }
1180
+
1181
+ /**
1182
+ * Checks if layout content is valid
1183
+ * Layouts need to have placeholders to be valid
1184
+ * @param string $layout_content Layout content (markup)
1185
+ * @return bool TRUE if layout is valid, FALSE otherwise
1186
+ */
1187
+ function is_valid_layout($layout_content) {
1188
+ $ph = $this->get_placeholder_defaults();
1189
+ return preg_match($ph->pattern_general, $layout_content);
1190
+ }
1191
+
1192
+ /**
1193
+ * Parse field layout with a regular expression
1194
+ * @param string $layout Layout data
1195
+ * @param string $search Regular expression pattern to search layout for
1196
+ * @return array Associative array containing all of the regular expression matches in the layout data
1197
+ * Array Structure:
1198
+ * root => placeholder tags
1199
+ * => Tag instances (array)
1200
+ * 'tag' => (string) tag name
1201
+ * 'match' => (string) placeholder match
1202
+ * 'attributes' => (array) attributes
1203
+ */
1204
+ function parse_layout($layout, $search) {
1205
+ $ph_xml = '';
1206
+ $parse_match = '';
1207
+ $ph_root_tag = 'ph_root_element';
1208
+ $ph_start_xml = '<';
1209
+ $ph_end_xml = ' />';
1210
+ $ph_wrap_start = '<' . $ph_root_tag . '>';
1211
+ $ph_wrap_end = '</' . $ph_root_tag . '>';
1212
+ $parse_result = false;
1213
+
1214
+ //Find all nested layouts in layout
1215
+ $match_value = preg_match_all($search, $layout, $parse_match, PREG_PATTERN_ORDER);
1216
+
1217
+ if ($match_value !== false && $match_value > 0) {
1218
+ $parse_result = array();
1219
+ //Get all matched elements
1220
+ $parse_match = $parse_match[1];
1221
+
1222
+ //Build XML string from placeholders
1223
+ foreach ($parse_match as $ph) {
1224
+ $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' ';
1225
+ }
1226
+ $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end;
1227
+ //Parse XML data
1228
+ $ph_prs = xml_parser_create();
1229
+ xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1);
1230
+ xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0);
1231
+ $ret = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']);
1232
+ xml_parser_free($ph_prs);
1233
+
1234
+ //Build structured array with all parsed data
1235
+
1236
+ unset($parse_result['index'][$ph_root_tag]);
1237
+
1238
+ //Build structured array
1239
+ $result = array();
1240
+ foreach ($parse_result['index'] as $tag => $instances) {
1241
+ $result[$tag] = array();
1242
+ //Instances
1243
+ foreach ($instances as $instance) {
1244
+ //Skip instance if it doesn't exist in parse results
1245
+ if (!isset($parse_result['values'][$instance]))
1246
+ continue;
1247
+
1248
+ //Stop processing instance if a previously-saved instance with the same options already exists
1249
+ foreach ($result[$tag] as $tag_match) {
1250
+ if ($tag_match['match'] == $parse_match[$instance - 1])
1251
+ continue 2;
1252
+ }
1253
+
1254
+ //Init instance data array
1255
+ $inst_data = array();
1256
+
1257
+ //Add Tag to array
1258
+ $inst_data['tag'] = $parse_result['values'][$instance]['tag'];
1259
+
1260
+ //Add instance data to array
1261
+ $inst_data['attributes'] = (isset($parse_result['values'][$instance]['attributes'])) ? $inst_data['attributes'] = $parse_result['values'][$instance]['attributes'] : '';
1262
+
1263
+ //Add match to array
1264
+ $inst_data['match'] = $parse_match[$instance - 1];
1265
+
1266
+ //Add to result array
1267
+ $result[$tag][] = $inst_data;
1268
+ }
1269
+ }
1270
+ $parse_result = $result;
1271
+ }
1272
+
1273
+ return $parse_result;
1274
+ }
1275
+
1276
+ /**
1277
+ * Retrieves default properties to use when evaluating layout placeholders
1278
+ * @return object Object with properties for evaluating layout placeholders
1279
+ */
1280
+ function get_placeholder_defaults() {
1281
+ $ph = new stdClass();
1282
+ $ph->start = '{';
1283
+ $ph->end = '}';
1284
+ $ph->reserved = array('ref' => 'ref_base');
1285
+ $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i';
1286
+ $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i';
1287
+ return $ph;
1288
+ }
1289
+
1290
+ /**
1291
+ * Build item output
1292
+ * @param string $layout (optional) Layout to build
1293
+ * @param string $data Data to pass to layout
1294
+ * @return string Generated output
1295
+ */
1296
+ function build($layout = 'form', $data = null) {
1297
+ $out = array(
1298
+ $this->build_pre($layout, $data),
1299
+ $this->build_layout($layout,$data),
1300
+ $this->build_post($layout, $data)
1301
+ );
1302
+ return implode('', $out);
1303
+ }
1304
+
1305
+ /**
1306
+ * Content to add before layout output
1307
+ * @return string
1308
+ */
1309
+ function build_pre($layout = 'form', $data = null) {
1310
+ return '';
1311
+ }
1312
+
1313
+ /**
1314
+ * Content to add after layout output
1315
+ * @return string
1316
+ */
1317
+ function build_post($layout = 'form', $data = null) {
1318
+ return '';
1319
+ }
1320
+
1321
+ /**
1322
+ * Builds HTML for a field based on its properties
1323
+ * @param string $layout (optional) Name of layout to build
1324
+ * @param array $data Additional data for current item
1325
+ */
1326
+ function build_layout($layout = 'form', $data = null) {
1327
+ $out_default = '';
1328
+ //Get base layout
1329
+ $out = $this->get_layout($layout);
1330
+ //Only parse valid layouts
1331
+ if ( $this->is_valid_layout($out) ) {
1332
+ //Parse Layout
1333
+ $ph = $this->get_placeholder_defaults();
1334
+
1335
+ //Search layout for placeholders
1336
+ while ( $ph->match = $this->parse_layout($out, $ph->pattern_general) ) {
1337
+ //Iterate through placeholders (tag, id, etc.)
1338
+ foreach ( $ph->match as $tag => $instances ) {
1339
+ //Iterate through instances of current placeholder
1340
+ foreach ( $instances as $instance ) {
1341
+ //Process value based on placeholder name
1342
+ $target_property = apply_filters($this->add_prefix('process_placeholder_' . $tag), '', $this, $instance, $layout, $data);
1343
+ //Process value using default processors (if necessary)
1344
+ if ( '' == $target_property ) {
1345
+ $target_property = apply_filters($this->add_prefix('process_placeholder'), $target_property, $this, $instance, $layout, $data);
1346
+ }
1347
+
1348
+ //Clear value if value not a string
1349
+ if ( !is_scalar($target_property) ) {
1350
+ $target_property = '';
1351
+ }
1352
+ //Replace layout placeholder with retrieved item data
1353
+ $out = str_replace($ph->start . $instance['match'] . $ph->end, $target_property, $out);
1354
+ }
1355
+ }
1356
+ }
1357
+ } else {
1358
+ $out = $out_default;
1359
+ }
1360
+ /* Return generated value */
1361
+ return $out;
1362
+ }
1363
+ }
1364
+
1365
+ class SLB_Field extends SLB_Field_Type {}
1366
+
1367
+ /**
1368
+ * Managed collection of fields
1369
+ * @package Simple Lightbox
1370
+ * @subpackage Fields
1371
+ * @author SM
1372
+ */
1373
+ class SLB_Field_Collection extends SLB_Field_Base {
1374
+
1375
+ /**
1376
+ * Indexed array of items in collection
1377
+ * @var array
1378
+ */
1379
+ var $items = array();
1380
+
1381
+ /**
1382
+ * Associative array of groups in content type
1383
+ * Key: Group name
1384
+ * Value: object of group properties
1385
+ * > title
1386
+ * > description string Group description
1387
+ * > items array Items in group
1388
+ * @var array
1389
+ */
1390
+ var $groups = array();
1391
+
1392
+ /**
1393
+ * Item type
1394
+ * @var string
1395
+ */
1396
+ var $item_type = 'SLB_Field';
1397
+
1398
+ /* Constructors */
1399
+
1400
+ /**
1401
+ * Legacy constructor
1402
+ * @uses __construct() to init instance
1403
+ * @param string $id Content type ID
1404
+ */
1405
+ function SLB_Field_Collection($id, $title = '', $properties = null) {
1406
+ $args = func_get_args();
1407
+ call_user_func_array(array(&$this, '__construct'), $args);
1408
+ }
1409
+
1410
+ /**
1411
+ * Class constructor
1412
+ * @param string $id Content type ID
1413
+ * @param array $properties (optional) Properties to set for content type (Default: none)
1414
+ */
1415
+ function __construct($id, $properties = null) {
1416
+ //Parent constructor
1417
+ parent::__construct($id, $properties);
1418
+
1419
+ //Init
1420
+ $this->init();
1421
+
1422
+ //Setup object based on properties
1423
+ if ( is_array($properties) && !empty($properties) ) {
1424
+ //Groups
1425
+ if ( isset($properties['groups']) )
1426
+ $this->add_groups($properties['groups']);
1427
+ //Items
1428
+ if ( isset($properties['items']) )
1429
+ $this->add_items($properties['items']);
1430
+ }
1431
+ }
1432
+
1433
+ /*-** Getters/Setters **-*/
1434
+
1435
+ /* Data */
1436
+
1437
+ /**
1438
+ * Retrieve external data for items in collection
1439
+ * Retrieved data is saved to the collection's $data property
1440
+ * Uses class properties to determine how data is retrieved
1441
+ * Examples:
1442
+ * > DB
1443
+ * > XML
1444
+ * > JSON
1445
+ * @return void
1446
+ */
1447
+ function load_data() {
1448
+ $this->data_fetched = true;
1449
+ }
1450
+
1451
+ /**
1452
+ * Set data for an item
1453
+ * @param string|object $item Reference or ID of Field to set data for
1454
+ * @param mixed $value Data to set
1455
+ */
1456
+ function set_data($item, $value = '', $save = true) {
1457
+ //Set data for entire collection
1458
+ if ( 1 == func_num_args() && is_array($item) )
1459
+ $this->data = wp_parse_args($item, $this->data);
1460
+ //Get $item's ID
1461
+ elseif ( is_object($item) && method_exists($item, 'get_id') )
1462
+ $item = $item->get_id();
1463
+ //Set data
1464
+ if ( is_string($item) && !empty($item) && isset($this->items[$item]) )
1465
+ $this->data[$item] = $value;
1466
+ if ( $save )
1467
+ $this->save();
1468
+ }
1469
+
1470
+ /* Item */
1471
+
1472
+ /**
1473
+ * Adds item to collection
1474
+ * @param string $id Unique name for item
1475
+ * @param object|string $parent Field type that this item is based on
1476
+ * @param array $properties (optional) Item properties
1477
+ * @param string $group (optional) Group ID to add item to
1478
+ * @return object Reference to new item
1479
+ */
1480
+ function &add($id, $parent = null, $properties = array(), $group = null) {
1481
+ $args = func_get_args();
1482
+ $properties = $this->make_properties($this->util->func_get_options($args), $properties, array('group' => $group));
1483
+ $it = ( is_object($id) ) ? 'O:' . $id->get_id() . '(' . get_class($id) . ')' : $id;
1484
+ //Check if previously created item is being added
1485
+ if ( is_object($id) && strtolower(get_class($id)) == strtolower($this->item_type) ) {
1486
+ $item =& $id;
1487
+ } else {
1488
+ //Create item
1489
+ if ( !class_exists($this->item_type) )
1490
+ return false;
1491
+ $type = $this->item_type;
1492
+ /**
1493
+ * @var SLB_Field
1494
+ */
1495
+ $item = new $type($id, $properties);
1496
+ }
1497
+ if ( strlen($item->get_id()) == 0 ) {
1498
+ return false;
1499
+ }
1500
+
1501
+ $item->set_container($this);
1502
+
1503
+ //Add item to collection
1504
+ $this->items[$item->get_id()] =& $item;
1505
+
1506
+ //Add item to group
1507
+ if ( empty($group) ) {
1508
+ //Check properties array for group
1509
+ if ( isset($properties['group']) ) {
1510
+ $group = $properties['group'];
1511
+ //Remove group property from array
1512
+ unset($properties['group']);
1513
+ }
1514
+ }
1515
+ $this->add_to_group($group, $item->id);
1516
+ return $item;
1517
+ }
1518
+
1519
+ /**
1520
+ * Removes item from collection
1521
+ * @param string|object $item Object or item ID to remove
1522
+ */
1523
+ function remove($item) {
1524
+ if ( $this->has($item) ) {
1525
+ $item = $this->get($item);
1526
+ $item = $item->get_id();
1527
+ //Remove from items array
1528
+ unset($this->items[$item]);
1529
+ //Remove item from groups
1530
+ $this->remove_from_group($item);
1531
+ }
1532
+ }
1533
+
1534
+ /**
1535
+ * Checks if item exists in the collection
1536
+ * @param string $item Item ID
1537
+ * @return bool TRUE if item exists, FALSE otherwise
1538
+ */
1539
+ function has($item) {
1540
+ return ( !is_string($item) || empty($item) || is_null($this->get_member_value('items', $item, null)) ) ? false : true;
1541
+ }
1542
+
1543
+ /**
1544
+ * Retrieve specified item in collection
1545
+ * @param string|object $item Item object or ID to retrieve
1546
+ * @return object Specified item
1547
+ */
1548
+ function &get($item) {
1549
+ if ( $this->has($item) ) {
1550
+ if ( !is_object($item) || !is_a($item, $this->item_type) ) {
1551
+ if ( is_string($item) ) {
1552
+ $item = trim($item);
1553
+ $item =& $this->items[$item];
1554
+ }
1555
+ else {
1556
+ $item = false;
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+ if ( empty($item) ) {
1562
+ //Return empty item if no item exists
1563
+ $item = new $this->item_type;
1564
+ }
1565
+ return $item;
1566
+ }
1567
+
1568
+ /**
1569
+ * Retrieve item data
1570
+ * @param $item
1571
+ * @param $context
1572
+ * @param $top
1573
+ */
1574
+ function get_data($item = null, $context = '', $top = true) {
1575
+ $this->load_data();
1576
+ $ret = null;
1577
+ if ( $this->has($item) ) {
1578
+ $item =& $this->get($item);
1579
+ $ret = $item->get_data($context, $top);
1580
+ } elseif ( is_null($item) ) {
1581
+ $ret = parent::get_data($context, $top);
1582
+ }
1583
+ return $ret;
1584
+ }
1585
+
1586
+ /* Items (Collection) */
1587
+
1588
+ /**
1589
+ * Add multiple items to collection
1590
+ * @param array $items Items to add to collection
1591
+ * Array Structure:
1592
+ * > Key (string): Item ID
1593
+ * > Val (array): Item properties
1594
+ * @return void
1595
+ */
1596
+ function add_items($items = array()) {
1597
+ //Validate
1598
+ if ( !is_array($items) || empty($items) )
1599
+ return false;
1600
+ //Iterate
1601
+ foreach ( $items as $id => $props ) {
1602
+ $this->add($id, $props);
1603
+ }
1604
+ }
1605
+
1606
+ /**
1607
+ * Retrieve reference to items in collection
1608
+ * @return array Collection items (reference)
1609
+ */
1610
+ function &get_items($group = null) {
1611
+ if ( $this->group_exists($group) ) {
1612
+ return $this->get_group_items($group);
1613
+ }
1614
+ return $this->items;
1615
+ }
1616
+
1617
+ /**
1618
+ * Default bulk item building method
1619
+ * Children classes should implement their own functionality
1620
+ * If no group specified, all items in collection are built
1621
+ * @param string|object $group (optional) Group to build items for (ID or instance object)
1622
+ * @return void
1623
+ */
1624
+ function build_items($group = null) {
1625
+ $items =& $this->get_items($group);
1626
+ $out = array();
1627
+ foreach ( $items as $item ) {
1628
+ $out[] = $item->build();
1629
+ }
1630
+ return implode('', $out);
1631
+ }
1632
+
1633
+ /* Group */
1634
+
1635
+ /**
1636
+ * Add groups to collection
1637
+ * @param array $groups Associative array of group properties
1638
+ * Array structure:
1639
+ * > Key (string): group ID
1640
+ * > Val (string): Group Title
1641
+ */
1642
+ function add_groups($groups = array()) {
1643
+ //Validate
1644
+ if ( !is_array($groups) || empty($groups) )
1645
+ return false;
1646
+ //Iterate
1647
+ foreach ( $groups as $id => $props ) {
1648
+ $this->add_group($id, $props);
1649
+ }
1650
+ }
1651
+
1652
+ /**
1653
+ * Adds group to content type
1654
+ * Groups are used to display related items in the UI
1655
+ * @param string $id Unique name for group
1656
+ * @param string $title Group title
1657
+ * @param string $description Short description of group's purpose
1658
+ * @param array $items (optional) ID's of existing items to add to group
1659
+ * @return object Group object
1660
+ */
1661
+ function &add_group($id, $title = '', $description = '', $items = array()) {
1662
+ //Create new group and set properties
1663
+ $id = trim($id);
1664
+ $this->groups[$id] =& $this->create_group($title, $description);
1665
+ //Add items to group (if supplied)
1666
+ if ( !empty($items) && is_array($items) )
1667
+ $this->add_to_group($id, $items);
1668
+ return $this->groups[$id];
1669
+ }
1670
+
1671
+ /**
1672
+ * Remove specified group from content type
1673
+ * @param string $id Group ID to remove
1674
+ */
1675
+ function remove_group($id) {
1676
+ $id = trim($id);
1677
+ if ( $this->group_exists($id) ) {
1678
+ unset($this->groups[$id]);
1679
+ }
1680
+ }
1681
+
1682
+ /**
1683
+ * Standardized method to create a new item group
1684
+ * @param string $title Group title (used in meta boxes, etc.)
1685
+ * @param string $description Short description of group's purpose
1686
+ * @return object Group object
1687
+ */
1688
+ function &create_group($title = '', $description = '') {
1689
+ //Create new group object
1690
+ $group = new stdClass();
1691
+ /* Set group properties */
1692
+
1693
+ //Set Title
1694
+ $title = ( is_scalar($title) ) ? trim($title) : '';
1695
+ $group->title = $title;
1696
+ //Set Description
1697
+ $description = ( is_scalar($description) ) ? trim($description) : '';
1698
+ $group->description = $description;
1699
+ //Create array to hold items
1700
+ $group->items = array();
1701
+ return $group;
1702
+ }
1703
+
1704
+ /**
1705
+ * Checks if group exists in collection
1706
+ * @param string $id Group name
1707
+ * @return bool TRUE if group exists, FALSE otherwise
1708
+ */
1709
+ function group_exists($group) {
1710
+ $ret = false;
1711
+ if ( is_object($group) )
1712
+ $ret = true;
1713
+ elseif ( is_string($group) && ($group = trim($group)) && strlen($group) > 0 ) {
1714
+ $group = trim($group);
1715
+ //Check if group exists
1716
+ $ret = !is_null($this->get_member_value('groups', $group, null));
1717
+ }
1718
+ return $ret;
1719
+ }
1720
+
1721
+ /**
1722
+ * Adds item to a group in the collection
1723
+ * Group is created if it does not already exist
1724
+ * @param string|array $group ID of group (or group parameters if new group) to add item to
1725
+ * @param string|array $items Name or array of item(s) to add to group
1726
+ */
1727
+ function add_to_group($group, $items) {
1728
+ //Validate parameters
1729
+ $group_id = '';
1730
+ if ( !empty($group) ) {
1731
+ if ( !is_array($group) ) {
1732
+ $group = array($group, $group);
1733
+ }
1734
+
1735
+ $group[0] = $group_id = trim(sanitize_title_with_dashes($group[0]));
1736
+ }
1737
+ if ( empty($group_id) || empty($items) )
1738
+ return false;
1739
+ //Create group if it doesn't exist
1740
+ if ( !$this->group_exists($group_id) ) {
1741
+ call_user_func_array($this->m('add_group'), $group);
1742
+ }
1743
+ if ( ! is_array($items) )
1744
+ $items = array($items);
1745
+ foreach ( $items as $item ) {
1746
+ if ( ! $this->has($item) )
1747
+ continue;
1748
+ $iref =& $this->get($item);
1749
+ //Remove item from any other group it's in (items can only be in one group)
1750
+ foreach ( array_keys($this->groups) as $group_name ) {
1751
+ if ( isset($this->groups[$group_name]->items[$iref->id]) )
1752
+ unset($this->groups[$group_name]->items[$iref->id]);
1753
+ }
1754
+ //Add reference to item in group
1755
+ $this->groups[$group_id]->items[$iref->id] =& $iref;
1756
+ unset($iref);
1757
+ }
1758
+ }
1759
+
1760
+ /**
1761
+ * Remove item from a group
1762
+ * If no group is specified, then item is removed from all groups
1763
+ * @param string|object $item Object or ID of item to remove from group
1764
+ * @param string $group (optional) Group ID to remove item from
1765
+ */
1766
+ function remove_from_group($item, $group = '') {
1767
+ //Get ID of item to remove or stop execution if item invalid
1768
+ $item = $this->get($item);
1769
+ $item = $item->get_id();
1770
+ if ( !$item )
1771
+ return false;
1772
+
1773
+ //Remove item from group
1774
+ if ( !empty($group) ) {
1775
+ //Remove item from single group
1776
+ if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
1777
+ unset($group->items[$item]);
1778
+ }
1779
+ } else {
1780
+ //Remove item from all groups
1781
+ foreach ( array_keys($this->groups) as $group ) {
1782
+ if ( ($group =& $this->get_group($group)) && isset($group->items[$item]) ) {
1783
+ unset($group->items[$item]);
1784
+ }
1785
+ }
1786
+ }
1787
+ }
1788
+
1789
+ /**
1790
+ * Retrieve specified group
1791
+ * @param string $group ID of group to retrieve
1792
+ * @return object Reference to specified group
1793
+ */
1794
+ function &get_group($group) {
1795
+ if ( is_object($group) )
1796
+ return $group;
1797
+ if ( is_string($group) )
1798
+ $group = trim($group);
1799
+ //Create group if it doesn't already exist
1800
+ if ( ! $this->group_exists($group) )
1801
+ $this->add_group($group);
1802
+ return $this->get_member_value('groups', $group);
1803
+ }
1804
+
1805
+ /**
1806
+ * Retrieve a group's items
1807
+ * @uses SLB_Field_Collection::get_group() to retrieve group object
1808
+ * @param object|string $group Group object or group ID
1809
+ * @return array Group's items
1810
+ */
1811
+ function &get_group_items($group) {
1812
+ $group =& $this->get_group($group);
1813
+ return $group->items;
1814
+ }
1815
+
1816
+ /**
1817
+ * Retrieve all groups in content type
1818
+ * @return array Reference to group objects
1819
+ */
1820
+ function &get_groups() {
1821
+ return $this->get_member_value('groups');
1822
+ }
1823
+
1824
+ /**
1825
+ * Output items in a group
1826
+ * @param string $group ID of Group to output
1827
+ * @return string Group output
1828
+ * @todo Refactor to be general builder
1829
+ */
1830
+ function build_group($group) {
1831
+ $out = array();
1832
+ $classnames = (object) array(
1833
+ 'multi' => 'multi_field',
1834
+ 'single' => 'single_field',
1835
+ 'elements' => 'has_elements'
1836
+ );
1837
+
1838
+ //Stop execution if group does not exist
1839
+ if ( $this->group_exists($group) && $group =& $this->get_group($group) ) {
1840
+ $group_items = ( count($group->items) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->items) ) && ( $f =& $group->items[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' );
1841
+ $classname = array($this->add_prefix('attributes_wrap'), $group_items);
1842
+ $out[] = '<div class="' . implode(' ', $classname) . '">'; //Wrap all items in group
1843
+
1844
+ //Build layout for each item in group
1845
+ foreach ( array_keys($group->items) as $item_id ) {
1846
+ $item =& $group->items[$item_id];
1847
+ $item->set_caller($this);
1848
+ //Start item output
1849
+ $id = $this->add_prefix('field_' . $item->get_id());
1850
+ $out[] = '<div id="' . $id . '_wrap" class=' . $this->add_prefix('attribute_wrap') . '>';
1851
+ //Build item layout
1852
+ $out[] = $item->build_layout();
1853
+ //end item output
1854
+ $out[] = '</div>';
1855
+ $item->clear_caller();
1856
+ }
1857
+ $out[] = '</div>'; //Close items container
1858
+ //Add description if exists
1859
+ if ( !empty($group->description) )
1860
+ $out[] = '<p class=' . $this->add_prefix('group_description') . '>' . $group->description . '</p>';
1861
+ }
1862
+
1863
+ //Return group output
1864
+ return implode($out);
1865
+ }
1866
+
1867
+ /* Collection */
1868
+
1869
+ /**
1870
+ * Build entire collection of items
1871
+ */
1872
+ function build() {
1873
+ //Get Groups
1874
+ $groups = array_keys($this->get_groups());
1875
+ //Build groups
1876
+ foreach ( $groups as $group ) {
1877
+ $this->build_group($group);
1878
+ }
1879
+ }
1880
+ }
1881
+
1882
+ /**
1883
+ * Collection of default system-wide fields
1884
+ * @package Simple Lightbox
1885
+ * @subpackage Fields
1886
+ * @author SM
1887
+ *
1888
+ */
1889
+ class SLB_Fields extends SLB_Field_Collection {
1890
+
1891
+ var $item_type = 'SLB_Field_Type';
1892
+
1893
+ /**
1894
+ * Placeholder handlers
1895
+ * @var array
1896
+ */
1897
+ var $placholders = null;
1898
+
1899
+ /* Constructor */
1900
+
1901
+ function SLB_Fields() {
1902
+ $this->__construct();
1903
+ }
1904
+
1905
+ function __construct() {
1906
+ parent::__construct('fields');
1907
+ }
1908
+
1909
+ function register_hooks() {
1910
+ parent::register_hooks();
1911
+
1912
+ //Init fields
1913
+ add_action('init', $this->m('register_types'));
1914
+ //Init placeholders
1915
+ add_action('init', $this->m('register_placeholders'));
1916
+ }
1917
+
1918
+ /* Field Types */
1919
+
1920
+ /**
1921
+ * Initialize fields and content types
1922
+ */
1923
+ function register_types() {
1924
+ /* Field Types */
1925
+
1926
+ //Base
1927
+ $base = new SLB_Field_Type('base');
1928
+ $base->set_description('Default Element');
1929
+ $base->set_property('tag', 'span');
1930
+ $base->set_property('class', '', 'attr');
1931
+ $base->set_layout('form_attr', '{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"}');
1932
+ $base->set_layout('form', '<{form_attr ref_base="layout"} />');
1933
+ $base->set_layout('label', '<label for="{field_id}">{label}</label>');
1934
+ $base->set_layout('display', '{data context="display"}');
1935
+ $this->add($base);
1936
+
1937
+ //Base closed
1938
+ $base_closed = new SLB_Field_Type('base_closed');
1939
+ $base_closed->set_parent('base');
1940
+ $base_closed->set_description('Default Element (Closed Tag)');
1941
+ $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>');
1942
+ $base_closed->set_layout('form_end', '</{tag}>');
1943
+ $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}');
1944
+ $this->add($base_closed);
1945
+
1946
+ //Input
1947
+ $input = new SLB_Field_Type('input', 'base');
1948
+ $input->set_description('Default Input Element');
1949
+ $input->set_property('tag', 'input');
1950
+ $input->set_property('type', 'text', 'attr');
1951
+ $input->set_property('value', '{data}', 'attr');
1952
+ $this->add($input);
1953
+
1954
+ //Text input
1955
+ $text = new SLB_Field_Type('text', 'input');
1956
+ $text->set_description('Text Box');
1957
+ $text->set_property('size', 15, 'attr');
1958
+ $text->set_property('label');
1959
+ $text->set_layout('form', '{label ref_base="layout"} {inherit}');
1960
+ $this->add($text);
1961
+
1962
+ //Checkbox
1963
+ $cb = new SLB_Field_Type('checkbox', 'input');
1964
+ $cb->set_property('type', 'checkbox');
1965
+ $cb->set_property('value', null);
1966
+ $cb->set_layout('form_attr', '{inherit} {checked}');
1967
+ $cb->set_layout('form', '{label ref_base="layout"} <{form_attr ref_base="layout"} />');
1968
+ $this->add($cb);
1969
+
1970
+ //Textarea
1971
+ $ta = new SLB_Field_Type('textarea', 'base_closed');
1972
+ $ta->set_property('tag', 'textarea');
1973
+ $ta->set_property('cols', 40, 'attr');
1974
+ $ta->set_property('rows', 3, 'attr');
1975
+ $this->add($ta);
1976
+
1977
+ //Rich Text
1978
+ $rt = new SLB_Field_Type('richtext', 'textarea');
1979
+ $rt->set_property('class', 'theEditor {inherit}');
1980
+ $rt->set_layout('form', '<div class="rt_container">{inherit}</div>');
1981
+ $rt->add_action('admin_print_footer_scripts', 'wp_tiny_mce', 25);
1982
+ $this->add($rt);
1983
+
1984
+ //Hidden
1985
+ $hidden = new SLB_Field_Type('hidden');
1986
+ $hidden->set_parent('input');
1987
+ $hidden->set_description('Hidden Field');
1988
+ $hidden->set_property('type', 'hidden');
1989
+ $this->add($hidden);
1990
+
1991
+ //Select
1992
+ $select = new SLB_Field_Type('select', 'base_closed');
1993
+ $select->set_description('Select tag');
1994
+ $select->set_property('tag', 'select');
1995
+ $select->set_property('tag_option', 'option');
1996
+ $select->set_property('options', array());
1997
+ $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{option_loop ref_base="layout"}{form_end ref_base="layout"}');
1998
+ $select->set_layout('option_loop', '{loop data="properties.options" layout="option" layout_data="option_data"}');
1999
+ $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value"}">{data_ext id="option_text"}</{tag_option}>');
2000
+ $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value"}" selected="selected">{data_ext id="option_text"}</{tag_option}>');
2001
+ $this->add($select);
2002
+
2003
+ //Span
2004
+ $span = new SLB_Field_Type('span', 'base_closed');
2005
+ $span->set_description('Inline wrapper');
2006
+ $span->set_property('tag', 'span');
2007
+ $span->set_property('value', 'Hello there!');
2008
+ $this->add($span);
2009
+
2010
+ //Enable plugins to modify (add, remove, etc.) field types
2011
+ do_action_ref_array($this->add_prefix('register_fields'), array(&$this));
2012
+
2013
+ //Signal completion of field registration
2014
+ do_action_ref_array($this->add_prefix('fields_registered'), array(&$this));
2015
+ }
2016
+
2017
+ /* Placeholder handlers */
2018
+
2019
+ function register_placeholders() {
2020
+ //Default placeholder handlers
2021
+ $this->register_placeholder('all', $this->m('process_placeholder_default'), 11);
2022
+ $this->register_placeholder('field_id', $this->m('process_placeholder_id'));
2023
+ $this->register_placeholder('field_name', $this->m('process_placeholder_name'));
2024
+ $this->register_placeholder('data', $this->m('process_placeholder_data'));
2025
+ $this->register_placeholder('data_ext',$this->m('process_placeholder_data_ext'));
2026
+ $this->register_placeholder('loop', $this->m('process_placeholder_loop'));
2027
+ $this->register_placeholder('label', $this->m('process_placeholder_label'));
2028
+ $this->register_placeholder('checked', $this->m('process_placeholder_checked'));
2029
+
2030
+ //Allow other code to register placeholders
2031
+ do_action_ref_array($this->add_prefix('register_field_placeholders'), array(&$this));
2032
+
2033
+ //Signal completion of field placeholder registration
2034
+ do_action_ref_array($this->add_prefix('field_placeholders_registered'), array(&$this));
2035
+ }
2036
+
2037
+ /**
2038
+ * Register a function to handle a placeholder
2039
+ * Multiple handlers may be registered for a single placeholder
2040
+ * Adds filter hook to WP for handling specified placeholder
2041
+ * Placeholders are in layouts and are replaced with data at runtime
2042
+ * @uses add_filter()
2043
+ * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders
2044
+ * @param callback $callback Function to set as a handler
2045
+ * @param int $priority (optional) Priority of handler
2046
+ * @return void
2047
+ */
2048
+ function register_placeholder($placeholder, $callback, $priority = 10) {
2049
+ if ( 'all' == $placeholder )
2050
+ $placeholder = '';
2051
+ else
2052
+ $placeholder = '_' . $placeholder;
2053
+ $hook = $this->add_prefix('process_placeholder' . $placeholder);
2054
+ add_filter($hook, $callback, $priority, 5);
2055
+ }
2056
+
2057
+ /**
2058
+ * Default placeholder processing
2059
+ * To be executed when current placeholder has not been handled by another handler
2060
+ * @param string $output Value to be used in place of placeholder
2061
+ * @param SLB_Field $item Field containing placeholder
2062
+ * @param array $placeholder Current placeholder
2063
+ * @see SLB_Field::parse_layout for structure of $placeholder array
2064
+ * @param string $layout Layout to build
2065
+ * @param array $data Extended data for item
2066
+ * @return string Value to use in place of current placeholder
2067
+ */
2068
+ function process_placeholder_default($output, $item, $placeholder, $layout, $data) {
2069
+ //Validate parameters before processing
2070
+ if ( empty($output) && is_a($item, 'SLB_Field_Type') && is_array($placeholder) ) {
2071
+ //Build path to replacement data
2072
+ $output = $item->get_member_value($placeholder);
2073
+
2074
+ //Check if value is group (properties, etc.)
2075
+ //All groups must have additional attributes (beyond reserved attributes) that define how items in group are used
2076
+ if (is_array($output)
2077
+ && !empty($placeholder['attributes'])
2078
+ && is_array($placeholder['attributes'])
2079
+ && ($ph = $item->get_placeholder_defaults())
2080
+ && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved))
2081
+ ) {
2082
+ /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */
2083
+
2084
+ //Find items matching criteria in $output
2085
+ //Check for group criteria
2086
+ if ( 'properties' == $placeholder['tag'] && ($prop_group = $item->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) {
2087
+ /* Process group */
2088
+ $group_out = array();
2089
+ //Iterate through properties in group and build string
2090
+ foreach ( array_keys($prop_group) as $prop_key ) {
2091
+ $prop_val = $item->get_property($prop_key);
2092
+ if ( !is_null($prop_val) )
2093
+ $group_out[] = $prop_key . '="' . $prop_val . '"';
2094
+ }
2095
+ $output = implode(' ', $group_out);
2096
+ }
2097
+ } elseif ( is_object($output) && is_a($output, $item->base_class) ) {
2098
+ /* Targeted property is actually a nested item */
2099
+ //Set caller to current item
2100
+ $output->set_caller($item);
2101
+ //Build layout for nested element
2102
+ $output = $output->build_layout($layout);
2103
+ }
2104
+ }
2105
+
2106
+ return $output;
2107
+ }
2108
+
2109
+ /**
2110
+ * Build Field ID attribute
2111
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2112
+ * @return string Placeholder output
2113
+ */
2114
+ function process_placeholder_id($output, $item, $placeholder, $layout, $data) {
2115
+ //Get attributes
2116
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id'));
2117
+ return $item->get_id($args);
2118
+ }
2119
+
2120
+ /**
2121
+ * Build Field name attribute
2122
+ * Name is formatted as an associative array for processing by PHP after submission
2123
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2124
+ * @return string Placeholder output
2125
+ */
2126
+ function process_placeholder_name($output, $item, $placeholder, $layout, $data) {
2127
+ //Get attributes
2128
+ $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_name'));
2129
+ return $item->get_id($args);
2130
+ }
2131
+
2132
+ /**
2133
+ * Build item label
2134
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
2135
+ * @return string Field label
2136
+ */
2137
+ function process_placeholder_label($output, $item, $placeholder, $layout, $data) {
2138
+ //Check if item has label property (e.g. sub-elements)
2139
+ $out = $item->get_property('label');
2140
+ //If property not set, use item title
2141
+ if ( empty($out) )
2142
+ $out = $item->get_title();
2143
+ return $out;
2144
+ }
2145
+
2146
+ /**
2147
+ * Retrieve data for item
2148
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2149
+ * @return string Placeholder output
2150
+ */
2151
+ function process_placeholder_data($output, $item, $placeholder, $layout) {
2152
+ $attr_default = array (
2153
+ 'context' => '',
2154
+ );
2155
+ $opts = wp_parse_args($placeholder['attributes'], $attr_default);
2156
+ //Save context to separate variable
2157
+ $context = $opts['context'];
2158
+ unset($opts['context']);
2159
+ //Get data
2160
+ $out = $item->get_data($opts);
2161
+ if ( !is_null($out) ) {
2162
+ //Get specific member in value (e.g. value from a specific item element)
2163
+ if ( isset($opts['element']) && is_array($out) && ( $el = $opts['element'] ) && isset($out[$el]) )
2164
+ $out = $out[$el];
2165
+ }
2166
+
2167
+ //Format data based on context
2168
+ $out = $item->format($out, $context);
2169
+ //Return data
2170
+ return $out;
2171
+ }
2172
+
2173
+ /**
2174
+ * Set checked attribute on item
2175
+ * Evaluates item's data to see if item should be checked or not
2176
+ * @see SLB_Fields::process_placeholder_default for parameter descriptions
2177
+ * @return string Appropriate checkbox attribute
2178
+ */
2179
+ function process_placeholder_checked($output, $item, $placeholder, $layout, $data) {
2180
+ $out = '';
2181
+ $c = $item->get_container();
2182
+ $d = ( isset($c->data[$item->get_id()]) ) ? $c->data[$item->get_id()] : null;
2183
+ $item->set_property('d', true);
2184
+ if ( $item->get_data() )
2185
+ $out = 'checked="checked"';
2186
+ $item->set_property('d', false);
2187
+ return $out;
2188
+ }
2189
+
2190
+ /**
2191
+ * Loops over data to build item output
2192
+ * Options:
2193
+ * data - Dot-delimited path in item that contains data to loop through
2194
+ * layout - Name of layout to use for each data item in loop
2195
+ * layout_data - Name of layout to use for data item that matches previously-saved item data
2196
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2197
+ * @return string Placeholder output
2198
+ */
2199
+ function process_placeholder_loop($output, $item, $placeholder, $layout, $data) {
2200
+ //Setup loop options
2201
+ $attr_defaults = array (
2202
+ 'layout' => '',
2203
+ 'layout_data' => null,
2204
+ 'data' => ''
2205
+ );
2206
+ $attr = wp_parse_args($placeholder['attributes'], $attr_defaults);
2207
+ if ( is_null($attr['layout_data']) )
2208
+ $attr['layout_data'] =& $attr['layout'];
2209
+ //Get data for loop
2210
+ $path = explode('.', $attr['data']);
2211
+ $loop_data = $item->get_member_value($path);
2212
+
2213
+ //Check if data is callback
2214
+ if ( is_callable($loop_data) )
2215
+ $loop_data = call_user_func($loop_data);
2216
+
2217
+ //Get item data
2218
+ $data = $item->get_data();
2219
+
2220
+ //Iterate over data and build output
2221
+ $out = array();
2222
+ if ( is_array($loop_data) && !empty($loop_data) ) {
2223
+ foreach ( $loop_data as $value => $label ) {
2224
+ //Load appropriate layout based on item value
2225
+ $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout'];
2226
+ //Stop processing if no valid layout is returned
2227
+ if ( empty($layout) )
2228
+ continue;
2229
+ //Prep extended item data
2230
+ $data_ext = array('option_value' => $value, 'option_text' => $label);
2231
+ $out[] = $item->build_layout($layout, $data_ext);
2232
+ }
2233
+ }
2234
+
2235
+ //Return output
2236
+ return implode($out);
2237
+ }
2238
+
2239
+ /**
2240
+ * Returns specified value from extended data array for item
2241
+ * @see SLB_Field_Type::process_placeholder_default for parameter descriptions
2242
+ * @return string Placeholder output
2243
+ */
2244
+ function process_placeholder_data_ext($output, $item, $placeholder, $layout, $data) {
2245
+ if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) {
2246
+ $output = strval($data[$key]);
2247
+ }
2248
+
2249
+ return $output;
2250
+ }
2251
+ }
includes/class.options.php ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'class.fields.php';
3
+
4
+ /**
5
+ * Option object
6
+ * @package Simple Lightbox
7
+ * @subpackage Options
8
+ * @author SM
9
+ */
10
+ class SLB_Option extends SLB_Field {
11
+
12
+ /**
13
+ * Child mapping
14
+ * @see SLB_Field_Base::map
15
+ * @var array
16
+ */
17
+ var $map = array(
18
+ 'default' => 'data',
19
+ 'attr' => 'properties'
20
+ );
21
+
22
+ /* Init */
23
+
24
+ function SLB_Option($id, $title = '', $default = '') {
25
+ $args = func_get_args();
26
+ call_user_func_array(array(&$this, '__construct'), $args);
27
+ }
28
+
29
+ /**
30
+ * @see SLB_Field::__construct()
31
+ * @uses parent::__construct() to initialize instance
32
+ * @param $id
33
+ * @param $title
34
+ * @param $default
35
+ */
36
+ function __construct($id, $title = '', $default = '') {
37
+ //Normalize properties
38
+ $args = func_get_args();
39
+ $props = SLB_Utilities::func_get_options($args);
40
+ $props = wp_parse_args($props, array ('id' => $id, 'title' => $title, 'default' => $default));
41
+ //Send to parent constructor
42
+ parent::__construct($props);
43
+ }
44
+
45
+ /* Getters/Setters */
46
+
47
+ /**
48
+ * Retrieve default value for option
49
+ * @return mixed Default option value
50
+ */
51
+ function get_default($context = '') {
52
+ return $this->get_data($context, false);
53
+ }
54
+
55
+ /**
56
+ * Sets parent based on default value
57
+ */
58
+ function set_parent($parent = null) {
59
+ $p =& $this->get_parent();
60
+ if ( empty($parent) && empty($p) ) {
61
+ $parent = 'text';
62
+ $d = $this->get_default();
63
+ if ( is_bool($d) )
64
+ $parent = 'checkbox';
65
+ $parent = 'option_' . $parent;
66
+ } elseif ( !empty($p) && !is_object($p) ) {
67
+ $parent =& $p;
68
+ }
69
+ parent::set_parent($parent);
70
+ }
71
+
72
+ /* Formatting */
73
+
74
+ /**
75
+ * Format data as string for browser output
76
+ * @see SLB_Field_Base::format()
77
+ * @param mixed $value Data to format
78
+ * @param string $context (optional) Current context
79
+ * @return string Formatted value
80
+ */
81
+ function format_display($value, $context = '') {
82
+ if ( !is_string($value) ) {
83
+ if ( is_bool($value) )
84
+ $value = ( $value ) ? 'Enabled' : 'Disabled';
85
+ elseif ( is_null($value) )
86
+ $value = '';
87
+ else
88
+ $value = strval($value);
89
+ }
90
+ return htmlentities($value);
91
+ }
92
+
93
+ /**
94
+ * Format data as boolean (true/false)
95
+ * @see SLB_Field_Base::format()
96
+ * @param mixed $value Data to format
97
+ * @param string $context (optional) Current context
98
+ * @return bool Option value
99
+ */
100
+ function format_bool($value, $context = '') {
101
+ if ( !is_bool($value) )
102
+ $value = !!$value;
103
+ return $value;
104
+ }
105
+
106
+ /**
107
+ * Format data as string
108
+ * @see SLB_Field_Base::format()
109
+ * @param mixed $value Data to format
110
+ * @param string $context (optional) Current context
111
+ * @return string Option string value
112
+ */
113
+ function format_string($value, $context = '') {
114
+ if ( is_bool($value) ) {
115
+ $value = ( $value ) ? 'true' : 'false';
116
+ }
117
+ elseif ( is_object($value) ) {
118
+ $value = get_class($value);
119
+ }
120
+ elseif ( is_array($value) ) {
121
+ $value = implode(' ', $value);
122
+ }
123
+ else {
124
+ $value = strval($value);
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Options collection
131
+ * @package Simple Lightbox
132
+ * @subpackage Options
133
+ * @author SM
134
+ * @uses SLB_Field_Collection
135
+ */
136
+ class SLB_Options extends SLB_Field_Collection {
137
+
138
+ /* Properties */
139
+
140
+ var $item_type = 'SLB_Option';
141
+
142
+ /**
143
+ * Key for saving version to DB
144
+ * @var string
145
+ */
146
+ var $version_key = 'version';
147
+
148
+
149
+ /**
150
+ * Whether verison has been checked
151
+ * @var bool
152
+ */
153
+ var $version_checked = false;
154
+
155
+ /* Init */
156
+
157
+ function SLB_Options($id = '', $props = array()) {
158
+ $args = func_get_args();
159
+ call_user_func_array(array(&$this, '__construct'), $args);
160
+ }
161
+
162
+ function __construct($id = '', $props = array()) {
163
+ $args = func_get_args();
164
+ //Validate arguments
165
+ if ( count($args) == 1 && is_array($args[0]) ) {
166
+ $props = $id;
167
+ $id = '';
168
+ }
169
+ //Set default ID
170
+ if ( !is_string($id) || empty($id) ) {
171
+ $id = 'options';
172
+ }
173
+ parent::__construct($id, $props);
174
+ $this->add_prefix_ref($this->version_key);
175
+ }
176
+
177
+ function register_hooks() {
178
+ parent::register_hooks();
179
+ //Register fields
180
+ add_action($this->add_prefix('register_fields'), $this->m('register_fields'));
181
+ //Set option parents
182
+ add_action($this->add_prefix('fields_registered'), $this->m('set_parents'));
183
+ }
184
+
185
+ /* Legacy/Migration */
186
+
187
+ /**
188
+ * Checks whether new version has been installed and migrates necessary settings
189
+ * @uses $version_key as option name
190
+ * @uses get_option() to retrieve saved version number
191
+ * @uses SLB_Utilities::get_plugin_version() to retrieve current version
192
+ * @return bool TRUE if version has been changed
193
+ */
194
+ function check_update() {
195
+ if ( !$this->version_checked ) {
196
+ $this->version_checked = true;
197
+ //Get version from DB
198
+ $vo = get_option($this->version_key);
199
+ //Get current version
200
+ $vn = $this->util->get_plugin_version();
201
+ //Compare versions
202
+ if ( $vo != $vn ) {
203
+ //Update saved version
204
+ $this->set_version($vn);
205
+ //Migrate old version to new version
206
+ if ( strcasecmp($vo, $vn) < 0 ) {
207
+ //Migrate
208
+ $this->migrate();
209
+ }
210
+ }
211
+ }
212
+ return $this->version_checked;
213
+ }
214
+
215
+ /**
216
+ * Save plugin version to DB
217
+ * If no version supplied, will fetch plugin data to determine version
218
+ * @uses $version_key as option name
219
+ * @uses update_option() to save version to options table
220
+ * @param string $ver (optional) Plugin version
221
+ */
222
+ function set_version($ver = null) {
223
+ if ( empty($ver) ) {
224
+ $ver = $this->util->get_plugin_version();
225
+ }
226
+ return update_option($this->version_key, $ver);
227
+ }
228
+
229
+ /**
230
+ * Migrate options from old versions to current version
231
+ */
232
+ function migrate() {
233
+ //Legacy options
234
+ $d = null;
235
+ $this->load_data();
236
+
237
+ //Migrate separate options to unified option
238
+ $items =& $this->get_items();
239
+ foreach ( $items as $id => $opt ) {
240
+ $oid = $this->add_prefix($id);
241
+ $o = get_option($oid, $d);
242
+ if ( $o !== $d ) {
243
+ //Migrate value to data array
244
+ $this->set_data($id, $o, false);
245
+ //Delete legacy option
246
+ delete_option($oid);
247
+ }
248
+ }
249
+
250
+ //Migrate legacy items
251
+ if ( is_array($this->properties_init) && isset($this->properties_init['legacy']) && is_array($this->properties_init['legacy']) ) {
252
+ foreach( $this->properties_init['legacy'] as $opt => $dest ) {
253
+ $oid = $this->add_prefix($opt);
254
+ $o = get_option($oid, $d);
255
+ //Only migrate valid values
256
+ if ( $o !== $d ) {
257
+ //Wrap single destination in array
258
+ if ( is_string($dest) ) {
259
+ $dest = array($dest);
260
+ }
261
+ //Process destinations
262
+ if ( is_array($dest) ) {
263
+ foreach ( $dest as $id ) {
264
+ $this->set_data($id, $o, false);
265
+ }
266
+ }
267
+ }
268
+ //Remove legacy item
269
+ delete_option($this->add_prefix($opt));
270
+ }
271
+ }
272
+ //Save changes
273
+ $this->save();
274
+ }
275
+
276
+ /* Option setup */
277
+
278
+ /**
279
+ * Register option-specific fields
280
+ * @param SLB_Fields $fields Reference to global fields object
281
+ * @return void
282
+ */
283
+ function register_fields(&$fields) {
284
+ //Layouts
285
+ $layout_label = '<label for="{field_id}" class="title block">{label}</label>';
286
+ $label_ref = '{label ref_base="layout"}';
287
+ $field_pre = '<div class="input block">';
288
+ $field_post = '</div>';
289
+ $opt_pre = '<div class="' . $this->add_prefix('option_item') . '">';
290
+ $opt_post = '</div>';
291
+ $layout_form = '<{form_attr ref_base="layout"} /> <span class="description">(Default: {data context="display" top="0"})</span>';
292
+
293
+ //Text input
294
+ $otxt = new SLB_Field_Type('option_text', 'text');
295
+ $otxt->set_property('class', '{inherit} code');
296
+ $otxt->set_property('size', null);
297
+ $otxt->set_property('value', '{data context="form"}');
298
+ $otxt->set_layout('label', $layout_label);
299
+ $otxt->set_layout('form', $opt_pre . $label_ref . $field_pre . $layout_form . $field_post . $opt_post);
300
+ $fields->add($otxt);
301
+
302
+ //Checkbox
303
+ $ocb = new SLB_Field_Type('option_checkbox', 'checkbox');
304
+ $ocb->set_layout('label', $layout_label);
305
+ $ocb->set_layout('form', $opt_pre . $label_ref . $field_pre . $layout_form . $field_post . $opt_post);
306
+ $fields->add($ocb);
307
+
308
+ //Theme
309
+ $othm = new SLB_Field_Type('option_theme', 'select');
310
+ $othm->set_layout('label', $layout_label);
311
+ $othm->set_layout('form_start', $field_pre . '{inherit}');
312
+ $othm->set_layout('form_end', '{inherit}' . $field_post);
313
+ $othm->set_layout('form', $opt_pre . '{inherit}' . $opt_post);
314
+ $fields->add($othm);
315
+ }
316
+
317
+ /**
318
+ * Set parent field types for options
319
+ * Parent only set for Admin pages
320
+ * @uses SLB_Option::set_parent() to set parent field for each option item
321
+ * @uses is_admin() to determine if current request is admin page
322
+ * @param object $fields Collection of default field types
323
+ * @return void
324
+ */
325
+ function set_parents(&$fields) {
326
+ if ( !is_admin() )
327
+ return false;
328
+ $items =& $this->get_items();
329
+ foreach ( array_keys($items) as $opt ) {
330
+ $items[$opt]->set_parent();
331
+ }
332
+ foreach ( $this->items as $opt ) {
333
+ $p = $opt->parent;
334
+ if ( is_object($p) )
335
+ $p = 'o:' . $p->id;
336
+ }
337
+ }
338
+
339
+ /* Processing */
340
+
341
+ function validate($values) {
342
+ if ( is_array($values) ) {
343
+ //Format data based on option type (bool, string, etc.)
344
+ foreach ( $values as $id => $val ) {
345
+ //Get default
346
+ $d = $this->get_default($id);
347
+ if ( is_bool($d) && !empty($val) )
348
+ $values[$id] = true;
349
+ }
350
+ //Merge in additional options that are not in post data
351
+ //Missing options (e.g. disabled checkboxes) & defaults
352
+ $items =& $this->get_items();
353
+ foreach ( $items as $id => $opt ) {
354
+ //Add options that were not included in form submission
355
+ if ( !array_key_exists($id, $values) ) {
356
+ if ( is_bool($opt->get_default()) )
357
+ $values[$id] = false;
358
+ else
359
+ $values[$id] = $opt->get_default();
360
+ }
361
+ }
362
+ }
363
+
364
+ //Return value
365
+ return $values;
366
+ }
367
+
368
+ /* Data */
369
+
370
+ /**
371
+ * Retrieve options from database
372
+ * @uses get_option to retrieve option data
373
+ * @return array Options data
374
+ */
375
+ function fetch_data($sanitize = true) {
376
+ //Check update
377
+ $this->check_update();
378
+ //Get data
379
+ $data = get_option($this->get_key(), null);
380
+ if ( $sanitize && is_array($data) ) {
381
+ //Sanitize loaded data based on default values
382
+ foreach ( $data as $id => $val ) {
383
+ if ( $this->has($id) ) {
384
+ $opt = $this->get($id);
385
+ if ( is_bool($opt->get_default()) )
386
+ $data[$id] = !!$val;
387
+ } else {
388
+ unset($data[$id]);
389
+ }
390
+ }
391
+ }
392
+ return $data;
393
+ }
394
+
395
+ /**
396
+ * Retrieves option data for collection
397
+ * @see SLB_Field_Collection::load_data()
398
+ */
399
+ function load_data() {
400
+ if ( !$this->data_fetched ) {
401
+ //Retrieve data
402
+ $this->data = $this->fetch_data();
403
+ $this->data_fetched = true;
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Resets option values to their default values
409
+ * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
410
+ */
411
+ function reset($hard = true) {
412
+ $this->load_data();
413
+ //Reset data
414
+ if ( $hard ) {
415
+ $this->data = null;
416
+ }
417
+ //Save
418
+ $this->save();
419
+ }
420
+
421
+ /**
422
+ * Save options data to database
423
+ */
424
+ function save() {
425
+ $opts =& $this->get_items();
426
+ $data = array();
427
+ foreach ( $opts as $id => $opt ) {
428
+ $data[$id] = $opt->get_data();
429
+ }
430
+ $this->data = $data;
431
+ update_option($this->get_key(), $data);
432
+ }
433
+
434
+ /* Collection */
435
+
436
+ /**
437
+ * Build key for saving/retrieving data to options table
438
+ * @return string Key
439
+ */
440
+ function get_key() {
441
+ return $this->add_prefix($this->get_id());
442
+ }
443
+
444
+ /**
445
+ * Add option to collection
446
+ * @uses SLB_Field_Collection::add() to add item
447
+ * @param string $id Unique item ID
448
+ * @param string $title Item title
449
+ * @param mixed $default Default value
450
+ * @param string $group (optional) Group ID to add item to
451
+ * @return SLB_Option Option instance reference
452
+ */
453
+ function &add($id, $title = '', $default = '', $group = null) {
454
+ //Build properties array
455
+ $properties = $this->make_properties($title, array('title' => $title, 'group' => $group, 'default' => $default));
456
+
457
+ //Create item
458
+ /**
459
+ * @var SLB_Option
460
+ */
461
+ $item =& parent::add($id, $properties);
462
+
463
+ return $item;
464
+ }
465
+
466
+ /**
467
+ * Retrieve option value
468
+ * @uses get_data() to retrieve option data
469
+ * @param string $option Option ID to retrieve value for
470
+ * @param string $context (optional) Context for formatting data
471
+ * @return mixed Option value
472
+ */
473
+ function get_value($option, $context = '') {
474
+ return $this->get_data($option, $context);
475
+ }
476
+
477
+ /**
478
+ * Retrieve option value as boolean (true/false)
479
+ * @uses get_data() to retrieve option data
480
+ * @param string $option Option ID to retrieve value for
481
+ * @return bool Option value
482
+ */
483
+ function get_bool($option) {
484
+ return $this->get_value($option, 'bool');
485
+ }
486
+
487
+ function get_string($option) {
488
+ return $this->get_value($option, 'string');
489
+ }
490
+
491
+ /**
492
+ * Retrieve option's default value
493
+ * @uses get_data() to retrieve option data
494
+ * @param string $option Option ID to retrieve value for
495
+ * @param string $context (optional) Context for formatting data
496
+ * @return mixed Option's default value
497
+ */
498
+ function get_default($option, $context = '') {
499
+ return $this->get_data($option, $context, false);
500
+ }
501
+
502
+ /* Output */
503
+
504
+ function build_group($group) {
505
+ if ( !$this->group_exists($group) )
506
+ return false;
507
+ $group =& $this->get_group($group);
508
+ //Stop processing if group contains no items
509
+ if ( !count($this->get_items($group)) )
510
+ return false;
511
+
512
+ //Group header
513
+ echo '<h4 class="subhead">' . $group->title . '</h4>';
514
+ //Build items
515
+ echo $this->build_items($group);
516
+ }
517
+ }
includes/class.utilities.php CHANGED
@@ -10,12 +10,35 @@
10
  */
11
  class SLB_Utilities {
12
 
13
- function SLB_Utilities() {
14
- $this->__construct();
15
- }
16
 
17
- function __construct() {
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
  /**
@@ -33,6 +56,423 @@ class SLB_Utilities {
33
 
34
  /* Helper Functions */
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  /*-** WP **-*/
37
 
38
  /**
@@ -59,14 +499,220 @@ class SLB_Utilities {
59
  return true;
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  /*-** Request **-*/
63
 
64
  /**
65
- * Checks $_SERVER['SCRIPT_NAME'] to see if file base name matches specified file name
66
  * @param string $filename Filename to check for
67
  * @return bool TRUE if current page matches specified filename, FALSE otherwise
68
  */
69
- function is_file( $filename ) {
70
  return ( $filename == basename( $_SERVER['SCRIPT_NAME'] ) );
71
  }
72
 
@@ -76,112 +722,267 @@ class SLB_Utilities {
76
  */
77
  function is_admin_management_page() {
78
  return ( is_admin()
79
- && ( $this->is_file('edit.php')
80
- || ( $this->is_file('admin.php')
81
  && isset($_GET['page'])
82
  && strpos($_GET['page'], 'cnr') === 0 )
83
  )
84
  );
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  /**
88
  * Joins and normalizes the slashes in the paths passed to method
89
  * All forward/back slashes are converted to forward slashes
90
  * Multiple path segments can be passed as additional argments
91
  * @param string $path Path to normalize
92
- * @param bool $trailing_slash (optional) Whether or not normalized path should have a trailing slash or not (Default: FALSE)
93
- * If multiple path segments are passed, $trailing_slash will be the LAST parameter (default value used if omitted)
 
94
  */
95
  function normalize_path($path, $trailing_slash = false) {
96
  $sl_f = '/';
97
  $sl_b = '\\';
98
  $parts = func_get_args();
 
 
99
  if ( func_num_args() > 1 ) {
100
- if ( is_bool(($tr = $parts[count($parts) - 1])) ) {
101
- $trailing_slash = $tr;
102
- //Remove from args array
 
 
 
 
 
103
  array_pop($parts);
104
- } else {
105
- $trailing_slash = false;
 
 
 
106
  }
107
- $first = true;
108
- //Trim trailing slashes from path parts
109
- foreach ( $parts as $key => $part ) {
110
- $part = trim($part);
111
- //Special Trim
112
- $parts[$key] = trim($part, $sl_f . $sl_b);
113
- //Verify path still contains value
114
- if ( empty($parts[$key]) ) {
115
- unset($parts[$key]);
116
- continue;
117
- }
118
- //Only continue processing the first valid path segment
119
- if ( $first )
120
- $first = !$first;
121
- else
122
- continue;
123
- //Add back leading slash if necessary
124
- if ( $part[0] == $sl_f || $part[0] == $sl_b )
125
- $parts[$key] = $sl_f . $parts[$key];
126
-
127
  }
128
  }
 
129
  //Join path parts together
130
  $parts = implode($sl_b, $parts);
131
  $parts = str_replace($sl_b, $sl_f, $parts);
132
  //Add trailing slash (if necessary)
133
  if ( $trailing_slash )
134
- $parts . $sl_f;
 
 
 
 
 
135
  return $parts;
136
  }
137
 
138
  /**
139
  * Returns URL of file (assumes that it is in plugin directory)
140
  * @param string $file name of file get URL
141
- * @return string File path
142
  */
143
  function get_file_url($file) {
144
- if (is_string($file) && '' != trim($file)) {
145
- $file = $this->normalize_path($this->get_url_base(), $file);
146
  }
147
  return $file;
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  /**
151
  * Retrieves file extension
152
  * @param string $file file name/path
 
153
  * @return string File's extension
154
  */
155
- function get_file_extension($file) {
156
  $ret = '';
157
  $sep = '.';
158
- if ( is_string($icon) && ( $rpos = strrpos($file, $sep) ) !== false )
 
 
159
  $ret = substr($file, $rpos + 1);
 
 
160
  return $ret;
161
  }
162
 
163
  /**
164
  * Checks if file has specified extension
 
165
  * @param string $file File name/path
166
- * @param string $extension File ending to check $file for
 
167
  * @return bool TRUE if file has extension
168
  */
169
- function has_file_extension($file, $extension) {
170
- return ( $this->get_file_extension($file) == $extension ) ? true : false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
 
173
  /**
174
  * Retrieve base URL for plugin-specific files
 
 
175
  * @return string Base URL
176
  */
177
  function get_url_base() {
178
  static $url_base = '';
179
  if ( '' == $url_base ) {
180
- $url_base = $this->normalize_path(WP_PLUGIN_URL, $this->get_plugin_base());
181
  }
182
  return $url_base;
183
  }
184
 
 
 
 
 
 
 
 
185
  function get_path_base() {
186
  static $path_base = '';
187
  if ( '' == $path_base ) {
@@ -189,23 +990,125 @@ class SLB_Utilities {
189
  }
190
  return $path_base;
191
  }
192
-
193
- function get_plugin_base() {
 
 
 
 
 
 
194
  static $plugin_dir = '';
195
  if ( '' == $plugin_dir ) {
196
  $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__))));
197
  }
 
 
198
  return $plugin_dir;
199
  }
200
 
 
 
 
 
 
 
201
  function get_plugin_base_file() {
202
- $file = 'main.php';
203
- return $this->get_path_base() . '/' . $file;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  }
205
 
 
 
 
 
 
 
 
206
  function get_plugin_base_name() {
207
- $file = $this->get_plugin_base_file();
208
- return plugin_basename($file);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  }
210
 
211
  /**
@@ -246,6 +1149,22 @@ class SLB_Utilities {
246
 
247
  /*-** General **-*/
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  /**
250
  * Checks if a property exists in a class or object
251
  * (Compatibility method for PHP 4
@@ -284,6 +1203,60 @@ class SLB_Utilities {
284
  }
285
  }
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  /**
288
  * Merges 1 or more arrays together
289
  * Methodology
@@ -295,7 +1268,6 @@ class SLB_Utilities {
295
  * - Merge item in base array with current item based on key name
296
  * - If the current item's value AND the corresponding item in the base array are BOTH arrays, recursively merge the the arrays
297
  * - If the current item's value OR the corresponding item in the base array is NOT an array, current item overwrites base item
298
- * @todo Append numerical elements (as opposed to overwriting element at same index in base array)
299
  * @param array Variable number of arrays
300
  * @param array $arr1 Default array
301
  * @return array Merged array
@@ -303,24 +1275,33 @@ class SLB_Utilities {
303
  function array_merge_recursive_distinct($arr1) {
304
  //Get all arrays passed to function
305
  $args = func_get_args();
306
- if (empty($args))
307
  return false;
 
 
 
308
  //Set first array as base array
309
  $merged = $args[0];
310
  //Iterate through arrays to merge
311
  $arg_length = count($args);
312
- for ($x = 1; $x < $arg_length; $x++) {
313
  //Skip if argument is not an array (only merge arrays)
314
- if (!is_array($args[$x]))
315
  continue;
316
  //Iterate through argument items
317
- foreach ($args[$x] as $key => $val) {
318
- if (!isset($merged[$key]) || !is_array($merged[$key]) || !is_array($val)) {
319
- $merged[$key] = $val;
320
- } elseif (is_array($merged[$key]) && is_array($val)) {
321
- $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $val);
322
- }
323
- //$merged[$key] = (is_array($val) && isset($merged[$key])) ? $this->array_merge_recursive_distinct($merged[$key], $val) : $val;
 
 
 
 
 
 
324
  }
325
  }
326
  return $merged;
@@ -375,6 +1356,13 @@ class SLB_Utilities {
375
  return $item;
376
  }
377
 
 
 
 
 
 
 
 
378
  function get_array_path($attribute = '', $format = null) {
379
  //Formatted value
380
  $fmtd = '';
@@ -440,6 +1428,34 @@ class SLB_Utilities {
440
  return $path;
441
  }
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  /**
444
  * Builds attribute string for HTML element
445
  * @param array $attr Attributes
@@ -460,6 +1476,11 @@ class SLB_Utilities {
460
  return $ret;
461
  }
462
 
 
 
 
 
 
463
  /**
464
  * Generate external stylesheet element
465
  * @param $url Stylesheet URL
@@ -470,6 +1491,33 @@ class SLB_Utilities {
470
  return $this->build_html_element(array('tag' => 'link', 'wrap' => false, 'attributes' => $attributes));
471
  }
472
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  /**
474
  * Generate external script element
475
  * @param $url Script URL
@@ -542,23 +1590,24 @@ class SLB_Utilities {
542
  //Get last submenu added
543
  $parent = $this->get_submenu_parent_file($parent);
544
  if ( isset($submenu[$parent]) ) {
545
- $subs =& $submenu[$parent];
546
- //Make sure menu isn't already in the desired position
547
- if ( $pos <= ( count($subs) - 1 ) ) {
548
- //Get submenu that was just added
549
- $sub = array_pop($subs);
550
- //Insert into desired position
551
- if ( 0 == $pos ) {
552
- array_unshift($subs, $sub);
553
- } else {
554
- $top = array_slice($subs, 0, $pos);
555
- $bottom = array_slice($subs, $pos);
556
- array_push($top, $sub);
557
- $subs = array_merge($top, $bottom);
558
- }
559
  }
560
  }
561
  }
 
562
 
563
  return $hookname;
564
  }
10
  */
11
  class SLB_Utilities {
12
 
13
+ /* Properties */
 
 
14
 
15
+ /**
16
+ * Instance parent
17
+ * @var object
18
+ */
19
+ var $parent = null;
20
+
21
+ /**
22
+ * Default plugin headers
23
+ * @var array
24
+ */
25
+ var $plugin_headers = array (
26
+ 'Name' => 'Plugin Name',
27
+ 'PluginURI' => 'Plugin URI',
28
+ 'Version' => 'Version',
29
+ 'Description' => 'Description',
30
+ 'Author' => 'Author',
31
+ 'AuthorURI' => 'Author URI',
32
+ 'TextDomain' => 'Text Domain',
33
+ 'DomainPath' => 'Domain Path',
34
+ 'Network' => 'Network',
35
+ );
36
+
37
+ /* Constructors */
38
+
39
+ function __construct(&$obj) {
40
+ if ( is_object($obj) )
41
+ $this->parent =& $obj;
42
  }
43
 
44
  /**
56
 
57
  /* Helper Functions */
58
 
59
+ /*-** Prefix **-*/
60
+
61
+ /**
62
+ * Get valid separator
63
+ * @param string $sep (optional) Separator supplied
64
+ * @return string Separator
65
+ */
66
+ function get_sep($sep = false) {
67
+ if ( is_null($sep) )
68
+ $sep = '';
69
+ return ( is_string($sep) ) ? $sep : '_';
70
+ }
71
+
72
+ /**
73
+ * Retrieve class prefix (with separator if set)
74
+ * @param bool|string $sep Separator to append to class prefix (Default: no separator)
75
+ * @return string Class prefix
76
+ */
77
+ function get_prefix($sep = null) {
78
+ $sep = $this->get_sep($sep);
79
+ $prefix = ( !empty($this->parent->prefix) ) ? $this->parent->prefix . $sep : '';
80
+ return $prefix;
81
+ }
82
+
83
+ /**
84
+ * Check if a string is prefixed
85
+ * @param string $text Text to check for prefix
86
+ * @param string $sep (optional) Separator used
87
+ */
88
+ function has_prefix($text, $sep = null) {
89
+ return ( !empty($text) && stripos($text, $this->get_prefix($sep)) === 0 );
90
+ }
91
+
92
+ /**
93
+ * Prepend plugin prefix to some text
94
+ * @param string $text Text to add to prefix
95
+ * @param string $sep (optional) Text used to separate prefix and text
96
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
97
+ * @return string Text with prefix prepended
98
+ */
99
+ function add_prefix($text, $sep = '_', $once = true) {
100
+ if ( $once && $this->has_prefix($text, $sep) )
101
+ return $text;
102
+ return $this->get_prefix($sep) . $text;
103
+ }
104
+
105
+ /**
106
+ * Prepend uppercased plugin prefix to some text
107
+ * @param string $text Text to add to prefix
108
+ * @param string $sep (optional) Text used to separate prefix and text
109
+ * @param bool $once (optional) Whether to add prefix to text that already contains a prefix or not
110
+ * @return string Text with prefix prepended
111
+ */
112
+ function add_prefix_uc($text, $sep = '_', $once = true) {
113
+ $args = func_get_args();
114
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
115
+ $pre = $this->get_prefix();
116
+ return str_replace($pre . $sep, strtoupper($pre) . $sep, $var);
117
+ }
118
+
119
+ /**
120
+ * Add prefix to variable reference
121
+ * Updates actual variable rather than return value
122
+ * @uses add_prefix() to add prefix to variable
123
+ * @param string $var Variable to add prefix to
124
+ * @param string $sep (optional) Separator text
125
+ * @param bool $once (optional) Add prefix only once
126
+ * @return void
127
+ */
128
+ function add_prefix_ref(&$var, $sep = null, $once = true) {
129
+ $args = func_get_args();
130
+ $var = call_user_func_array($this->m($this, 'add_prefix'), $args);
131
+ }
132
+
133
+ /**
134
+ * Remove prefix from specified string
135
+ * @param string $text String to remove prefix from
136
+ * @param string $sep (optional) Separator used with prefix
137
+ */
138
+ function remove_prefix($text, $sep = '_') {
139
+ if ( $this->has_prefix($text,$sep) )
140
+ $text = substr($text, strlen($this->get_prefix($sep)));
141
+ return $text;
142
+ }
143
+
144
+ /**
145
+ * Returns Database prefix for plugin-related DB Tables
146
+ * @return string Database prefix
147
+ */
148
+ function get_db_prefix() {
149
+ global $wpdb;
150
+ return $wpdb->prefix . $this->get_prefix('_');
151
+ }
152
+
153
+ /* Wrapped Values */
154
+
155
+ /**
156
+ * Returns validated object of start/end wrapper values
157
+ * @param string|array $start Start text (Can also be array defining start & end values)
158
+ * @param string $end (optional) End text
159
+ * If $end not defined, then $start is used
160
+ * @return obj Wrapper
161
+ */
162
+ function get_wrapper($start = null, $end = null) {
163
+ //Return pre-built wrapper
164
+ if ( is_object($start) && isset($start->start) && isset($start->end) )
165
+ return $start;
166
+ //Default wrapper
167
+ if ( is_null($start) && is_null($end) )
168
+ $start = array('[', ']');
169
+ $wrapper = compact('start', 'end');
170
+ if ( is_array($start) && count($start) > 1 ) {
171
+ $wrapper['start'] = $start[0];
172
+ $wrapper['end'] = $start[1];
173
+ }
174
+ if ( !is_string($wrapper['start']) || empty($wrapper['start'] ) )
175
+ $wrapper['start'] = '';
176
+ if ( !is_string($wrapper['end']) || empty($wrapper['end']) )
177
+ $wrapper['end'] = $wrapper['start'];
178
+
179
+ return (object) $wrapper;
180
+ }
181
+
182
+ /**
183
+ * Check if text is wrapped by specified character(s)
184
+ * @uses this->get_wrapper() to Validate wrapper text
185
+ * @param string $text Text to check
186
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
187
+ * @param string $end (optional) End text
188
+ */
189
+ function has_wrapper($text, $start = null, $end = null) {
190
+ if ( !is_string($text) || empty($text) )
191
+ return false;
192
+ //Validate wrapper)
193
+ $w = $this->get_wrapper($start, $end);
194
+
195
+ //Check for wrapper
196
+ return ( substr($text, 0, 1) == $w->start && substr($text, -1, 1) == $w->end ) ? true : false;
197
+ }
198
+
199
+ /**
200
+ * Remove wrapper from specified text
201
+ * @uses this->has_wrapper() to check if text is wrapped
202
+ * @uses this->get_wrapper() to retrieve wrapper object
203
+ * @param string $text Text to check
204
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
205
+ * @param string $end (optional) End text
206
+ * @return string Unwrapped text
207
+ */
208
+ function remove_wrapper($text, $start = null, $end = null) {
209
+ if ( $this->has_wrapper($text, $start, $end) ) {
210
+ $w = $this->get_wrapper($start, $end);
211
+ $text = substr($text, strlen($w->start), strlen($text) - strlen($w->start) - strlen($w->end) );
212
+ }
213
+
214
+ return $text;
215
+ }
216
+
217
+ /**
218
+ * Add wrapper to specified text
219
+ * @uses this->get_wrapper() to retrieve wrapper object
220
+ * @param string $text Text to wrap
221
+ * @param string|array $start (optional) Start text (Array defines both start/end text)
222
+ * @param string $end (optional) End text
223
+ * @param bool $once (optional) Whether to wrap text only once (Default: TRUE)
224
+ * @return string Wrapped text
225
+ */
226
+ function add_wrapper($text, $start = null, $end = null, $once = true) {
227
+ $w = $this->get_wrapper($start, $end);
228
+ if ( !$once || !$this->has_wrapper($text, $w) )
229
+ $text = $w->start . $text . $w->end;
230
+ return $text;
231
+ }
232
+
233
+ /*-** Client **-*/
234
+
235
+ /**
236
+ * Parses client files array
237
+ * > Adds ID property (prefixed file key)
238
+ * > Parses and validates internal dependencies
239
+ * > Converts properties array to object
240
+ * Properties
241
+ * > file (string|array): File name (string) or callback (array) to retrieve file name
242
+ * > deps (array) [optional]: Dependencies
243
+ * > Values wrapped in square brackets (`[` & `]`) are internal files
244
+ * > callback (string|array) [optional]: Global callback to determine whether file should be loaded
245
+ * > Values wrapped in square brackets (`[` & `]`) are internal methods (of parent object)
246
+ * > context (array) [optional]: Context(s) in which to load the file
247
+ * Acceptable values
248
+ * > string: Context name
249
+ * > array: Context name + callback (both must return TRUE to load file)
250
+ * > Callback follows same pattern as `callback` member
251
+ * @param array $files Files array
252
+ * @return object Client files
253
+ */
254
+ function parse_client_files($files, $type = 'scripts') {
255
+ if ( is_array($files) && !empty($files) ) {
256
+ //Defaults
257
+ $defaults = array(
258
+ 'file' => null,
259
+ 'deps' => array(),
260
+ 'callback' => null,
261
+ 'context' => array()
262
+ );
263
+ switch ( $type ) {
264
+ case 'styles':
265
+ $defaults['media'] = 'all';
266
+ break;
267
+ default:
268
+ $defaults['in_footer'] = false;
269
+ }
270
+ //Iterate through files
271
+ foreach ( $files as $h => $p ) {
272
+ unset($file, $cb, $ctxs, $ctx);
273
+ //Set ID
274
+ $p['id'] = $this->add_prefix($h);
275
+ //Type Validation
276
+ foreach ( $defaults as $m => $d ) {
277
+ //Check if value requires validation
278
+ if ( !is_array($d) || !isset($p[$m]) || is_array($p[$m]) )
279
+ continue;
280
+ //Wrap value in array or destroy it
281
+ if ( is_scalar($p[$m]) )
282
+ $p[$m] = array($p[$m]);
283
+ else
284
+ unset($p[$m]);
285
+ }
286
+
287
+ $p = array_merge($defaults, $p);
288
+
289
+ /* File name */
290
+
291
+ //Validate file
292
+ $file =& $p['file'];
293
+
294
+ //Determine if filename or callback
295
+ if ( !$this->is_file($file) )
296
+ $file = $this->parse_client_file_callback($file);
297
+ //Remove invalid file and move on to next
298
+ if ( empty($file) ) {
299
+ unset($files[$h]);
300
+ continue;
301
+ }
302
+
303
+ /* Dependencies */
304
+
305
+ //Format internal dependencies
306
+ foreach ( $p['deps'] as $idx => $dep ) {
307
+ if ( $this->has_wrapper($dep) ) {
308
+ $dep = $this->remove_wrapper($dep);
309
+ $p['deps'][$idx] = $this->add_prefix($dep);
310
+ }
311
+ }
312
+
313
+ /* Context */
314
+
315
+ //Validate callback
316
+ $cb =& $p['callback'];
317
+ if ( !is_null($cb) ) {
318
+ $cb = $this->parse_client_file_callback($cb);
319
+ //Remove files with invalid callbacks (will never be loaded)
320
+ if ( is_null($cb) ) {
321
+ unset($files[$h]);
322
+ continue;
323
+ }
324
+ }
325
+
326
+ //Validate contexts
327
+ $ctxs =& $p['context'];
328
+ $ctxs = array_unique($ctxs);
329
+ $has_contexts = ( count($ctxs) > 0 ) ? true : false;
330
+ foreach ( $ctxs as $idx => $ctx ) {
331
+ //Convert to array
332
+ $ctx = array_values( array_slice( (array) $ctx, 0, 2 ) );
333
+ switch ( count($ctx) ) {
334
+ case 1 :
335
+ //Simple context
336
+ $ctx = $ctx[0];
337
+ break;
338
+ case 2 :
339
+ //Context + Callback
340
+ $ctx[1] = $this->parse_client_file_callback($ctx[1]);
341
+ if ( !is_null($ctx[1]) ) {
342
+ break;
343
+ }
344
+ //Continue to default case if callback is invalid
345
+ default :
346
+ //Context is invalid
347
+ $ctx = false;
348
+ break;
349
+ }
350
+
351
+ //Remove invalid contexts
352
+ if ( empty($ctx) ) {
353
+ unset($ctxs[$idx]);
354
+ } else {
355
+ $ctxs[$idx] = $ctx;
356
+ }
357
+ }
358
+ //Remove file if all specified contexts invalid (no context is OK)
359
+ if ( $has_contexts && empty($ctxs) ) {
360
+ unset($files[$h]);
361
+ continue;
362
+ }
363
+ $ctxs = array_values($ctxs);
364
+
365
+ /* Finalize Properties */
366
+
367
+ //Convert properties to object
368
+ $files[$h] = (object) $p;
369
+ }
370
+ }
371
+ //Cast to object before returning
372
+ $files = (object) $files;
373
+ return $files;
374
+ }
375
+
376
+ /**
377
+ * Parses callbacks set for client files
378
+ * @param string $callback Callback value
379
+ * > Values wrapped in square brackets (`[` & `]`) are internal methods (of parent object)
380
+ * @return callback|null Validated callback (NULL if callback is invalid)
381
+ */
382
+ function parse_client_file_callback($callback) {
383
+ if ( $this->has_wrapper($callback) ) {
384
+ $callback = $this->m($this->parent, $this->remove_wrapper($callback));
385
+ }
386
+ if ( !is_callable($callback) )
387
+ $callback = null;
388
+ return $callback;
389
+ }
390
+
391
+ /**
392
+ * Build JS client object
393
+ * @param string (optional) $path Additional object path
394
+ * @return string Client object
395
+ */
396
+ function get_client_object($path = null) {
397
+ $obj = strtoupper($this->get_prefix());
398
+ if ( !empty($path) && is_string($path) ) {
399
+ if ( 0 !== strpos($path, '[') )
400
+ $obj .= '.';
401
+ $obj .= $path;
402
+ }
403
+ return $obj;
404
+ }
405
+
406
+ /**
407
+ * Build jQuery JS expression to add data to specified client object
408
+ * @param string|obj $obj Name of client object (Set to root object if not a valid name)
409
+ * @param mixed $data Data to add to client object
410
+ * @param bool (optional) $out Whether or not to output code (Default: false)
411
+ * @return string JS expression to extend client object
412
+ */
413
+ function extend_client_object($obj, $data = null, $out = false) {
414
+ //Validate parameters
415
+ $args = func_get_args();
416
+ switch ( count($args) ) {
417
+ case 2:
418
+ if ( !is_scalar($args[0]) ) {
419
+ if ( is_bool($args[1]) )
420
+ $out = $args[1];
421
+ } else {
422
+ break;
423
+ }
424
+ case 1:
425
+ $data = $args[0];
426
+ $obj = null;
427
+ break;
428
+ }
429
+ //Default client object
430
+ if ( !is_string($obj) || empty($obj) )
431
+ $obj = null;
432
+ //Default data
433
+ if ( is_array($data) )
434
+ $data = (object)$data;
435
+ //Build expression
436
+ if ( empty($data) || ( empty($obj) && is_scalar($data) ) ) {
437
+ $ret = '';
438
+ } else {
439
+ $ret = array();
440
+ //Validate object(s) being extended
441
+ $c_obj = $this->get_client_object($obj);
442
+ $sep = '.';
443
+ $c_obj = trim($c_obj, $sep);
444
+ //Start with full object
445
+ $objs = array($c_obj);
446
+ $offset = 0;
447
+ $len = strlen($c_obj);
448
+ //Add segments to array (in reverse)
449
+ while ( ( $pos = strrpos($c_obj, $sep, $offset) ) && $pos !== false ) {
450
+ $objs[] = substr($c_obj, 0, $pos);
451
+ $offset = $pos - $len - 1;
452
+ }
453
+
454
+ $condition = 'if ( ' . implode(' && ', array_reverse($objs)) . ' ) ';
455
+ $ret = $condition . '$.extend(' . $c_obj . ', ' . json_encode($data) . ');';
456
+ if ( $out )
457
+ echo $this->build_script_element($ret);
458
+ }
459
+ return $ret;
460
+ }
461
+
462
+ /**
463
+ * Build client method call
464
+ * @uses get_client_object() to generate the body of the method call
465
+ * @param string $method Method name
466
+ * @param mixed Parameters to pass to method (will be JSON-encoded)
467
+ * @return string Method call
468
+ */
469
+ function call_client_method($method, $params = null) {
470
+ if ( !$method )
471
+ return '';
472
+ $params = ( !is_null($params) ) ? json_encode($params) : '';
473
+ return $this->get_client_object($method) . '(' . $params. ');';
474
+ }
475
+
476
  /*-** WP **-*/
477
 
478
  /**
499
  return true;
500
  }
501
 
502
+ /* Hooks */
503
+
504
+ /**
505
+ * Retrieve parent object
506
+ * @return obj|bool Parent object (FALSE if no valid parent set)
507
+ */
508
+ function &get_parent() {
509
+ if ( is_object($this->parent) )
510
+ return $this->parent;
511
+ else
512
+ return false;
513
+ }
514
+
515
+ /**
516
+ * Retrieve parent property value
517
+ * @uses self::get_parent()
518
+ * @param string $prop Property name
519
+ * @param mixed $default Default value
520
+ * @return mixed Parent property value
521
+ */
522
+ function get_parent_property($prop, $default = '') {
523
+ $p =& $this->get_parent();
524
+ return ( !!$p && property_exists($p, $prop) ) ? $p->{$prop} : $default;
525
+ }
526
+
527
+ /**
528
+ * Retrieve formatted name for internal hooks
529
+ * Prefixes with parent prefix and hook prefix
530
+ * @uses self::get_parent_property() to retrieve hook prefix
531
+ * @uses self::add_prefix()
532
+ * @param string $tag Base tag
533
+ * @return string Formatted hook
534
+ */
535
+ function get_hook($tag) {
536
+ //Hook prefix
537
+ $hook = $this->get_parent_property('hook_prefix', '');
538
+ if ( !empty($hook) )
539
+ $hook .= '_';
540
+ //Prefix
541
+ return $this->add_prefix($hook . $tag);
542
+ }
543
+
544
+ /**
545
+ * Run internal action
546
+ * Namespaces $tag
547
+ * @uses self::get_hook()
548
+ * @see do_action()
549
+ */
550
+ function do_action($tag, $arg = '') {
551
+ $args = func_get_args();
552
+ $args[0] = $this->get_hook($tag);
553
+ return call_user_func_array('do_action', $args);
554
+ }
555
+
556
+ /**
557
+ * Run internal action passing arguments in array
558
+ * @uses do_action_ref_array()
559
+ */
560
+ function do_action_ref_array($tag, $args) {
561
+ return do_action_ref_array($this->get_hook($tag), $args);
562
+ }
563
+
564
+ /**
565
+ * Run internal filter
566
+ * Namespaces $tag
567
+ * @uses self::get_hook()
568
+ * @see apply_filters()
569
+ */
570
+ function apply_filters($tag, $value) {
571
+ $args = func_get_args();
572
+ $args[0] = $this->get_hook($tag);
573
+ return call_user_func_array('apply_filters', $args);
574
+ }
575
+
576
+ /**
577
+ * Run internal filter passing arguments in array
578
+ * @uses apply_filters_ref_array()
579
+ */
580
+ function apply_filters_ref_array($tag, $args) {
581
+ return apply_filters_ref_array($this->get_hook($tag), $args);
582
+ }
583
+
584
+ /**
585
+ * Add internal action
586
+ * Namespaces $tag
587
+ * @uses self::get_hook()
588
+ * @see add_action()
589
+ */
590
+ function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
591
+ return add_action($this->get_hook($tag), $function_to_add, $priority, $accepted_args);
592
+ }
593
+
594
+ /**
595
+ * Add internal filter
596
+ * Namespaces $tag
597
+ * @uses self::get_hook()
598
+ * @see add_filter()
599
+ */
600
+ function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
601
+ return add_filter($this->get_hook($tag), $function_to_add, $priority, $accepted_args);
602
+ }
603
+
604
+ /**
605
+ * Remove internal action
606
+ * Namespaces $tag
607
+ * @uses self::get_hook()
608
+ * @uses remove_action()
609
+ */
610
+ function remove_action($tag, $function_to_remove, $priority = 10, $accepted_args = 1) {
611
+ return remove_action($this->get_hook($tag), $function_to_remove, $priority, $accepted_args);
612
+ }
613
+
614
+ /**
615
+ * Remove internal filter
616
+ * Namespaces $tag
617
+ * @uses self::get_hook()
618
+ * @uses remove_filter()
619
+ */
620
+ function remove_filter($tag, $function_to_remove, $priority = 10, $accepted_args = 1) {
621
+ return remove_filter($this->get_hook($tag), $function_to_remove, $priority, $accepted_args);
622
+ }
623
+
624
+ /* Meta */
625
+
626
+ /**
627
+ * Retrieves post metadata for internal methods
628
+ * Metadata set internally is wrapped in an array so it is unwrapped before returned the retrieved value
629
+ * @see get_post_meta()
630
+ * @param int $post_id Post ID
631
+ * @param string $key Name of metadata to retrieve
632
+ * @param boolean $single Whether or not to retrieve single value or not
633
+ * @return mixed Retrieved post metadata
634
+ */
635
+ function post_meta_get($post_id, $key, $single = false) {
636
+ $meta_value = get_post_meta($post_id, $this->post_meta_get_key($key), $single);
637
+ if (is_array($meta_value) && count($meta_value) == 1)
638
+ $meta_value = $meta_value[0];
639
+ return $meta_value;
640
+ }
641
+
642
+ /**
643
+ * Wraps metadata in array for storage in database
644
+ * @param mixed $meta_value Value to be set as metadata
645
+ * @return array Wrapped metadata value
646
+ */
647
+ function post_meta_prepare_value($meta_value) {
648
+ return array($meta_value);
649
+ }
650
+
651
+ /**
652
+ * Adds Metadata for a post to database
653
+ * For internal methods
654
+ * @see add_post_meta
655
+ * @param $post_id
656
+ * @param $meta_key
657
+ * @param $meta_value
658
+ * @param $unique
659
+ * @return boolean Result of operation
660
+ */
661
+ function post_meta_add($post_id, $meta_key, $meta_value, $unique = false) {
662
+ $meta_value = $this->post_meta_value_prepare($meta_value);
663
+ return add_post_meta($post_id, $meta_key, $meta_value, $unique);
664
+ }
665
+
666
+ /**
667
+ * Updates post metadata for internal data/methods
668
+ * @see update_post_meta()
669
+ * @param $post_id
670
+ * @param $meta_key
671
+ * @param $meta_value
672
+ * @param $prev_value
673
+ * @return boolean Result of operation
674
+ */
675
+ function post_meta_update($post_id, $meta_key, $meta_value, $prev_value = '') {
676
+ $meta_value = $this->post_meta_prepare_value($meta_value);
677
+ return update_post_meta($post_id, $meta_key, $meta_value, $prev_value);
678
+ }
679
+
680
+ /**
681
+ * Builds postmeta key for custom data set by plugin
682
+ * @param string $key Base key name
683
+ * @return string Formatted postmeta key
684
+ */
685
+ function post_meta_get_key($key) {
686
+ $sep = '_';
687
+ if ( strpos($key, $sep . $this->prefix) !== 0 ) {
688
+ $key_base = func_get_args();
689
+ if ( !empty($key_base) ) {
690
+ $key = array_merge((array)$this->prefix, $key_base);
691
+ return $sep . implode($sep, $key);
692
+ }
693
+ }
694
+
695
+ return $key;
696
+ }
697
+
698
+ /**
699
+ * Creates a meta key for storing post meta data
700
+ * Prefixes standard prefixed text with underscore to hide meta data on post edit forms
701
+ * @param string $text Text to use as base of meta key
702
+ * @return string Formatted meta key
703
+ */
704
+ function make_meta_key($text = '') {
705
+ return '_' . $this->add_prefix($text);
706
+ }
707
+
708
  /*-** Request **-*/
709
 
710
  /**
711
+ * Checks if the currently executing file matches specified file name
712
  * @param string $filename Filename to check for
713
  * @return bool TRUE if current page matches specified filename, FALSE otherwise
714
  */
715
+ function is_current_file( $filename ) {
716
  return ( $filename == basename( $_SERVER['SCRIPT_NAME'] ) );
717
  }
718
 
722
  */
723
  function is_admin_management_page() {
724
  return ( is_admin()
725
+ && ( $this->is_current_file('edit.php')
726
+ || ( $this->is_current_file('admin.php')
727
  && isset($_GET['page'])
728
  && strpos($_GET['page'], 'cnr') === 0 )
729
  )
730
  );
731
  }
732
 
733
+ function is_a($obj, $class_name) {
734
+ return ( is_object($obj) && is_a($obj, $this->add_prefix_uc($class_name)) ) ? true : false;
735
+ }
736
+
737
+ /* Context */
738
+
739
+ /**
740
+ * Retrieve context for current request
741
+ * @return array Context
742
+ */
743
+ function get_context() {
744
+ //Context
745
+ static $ctx = null;
746
+ if ( !is_array($ctx) ) {
747
+ //Standard
748
+ $ctx = array($this->build_context());
749
+ //Action
750
+ $action = $this->get_action();
751
+ if ( !empty($action) )
752
+ $ctx[] = $this->build_context('action', $action);
753
+ //Admin page
754
+ if ( is_admin() ) {
755
+ global $pagenow;
756
+ $pg = $this->strip_file_extension($pagenow);
757
+ $ctx[] = $this->build_context('page', $pg);
758
+ if ( !empty($action) )
759
+ $ctx[] = $this->build_context('page', $pg, 'action', $action);
760
+ }
761
+ //User
762
+ $u = wp_get_current_user();
763
+ $ctx[] = $this->build_context('user', ( $u->ID ) ? 'registered' : 'guest', false);
764
+ }
765
+
766
+ return $ctx;
767
+ }
768
+
769
+ /**
770
+ * Builds context from multiple components
771
+ * Usage:
772
+ * > $prefix can be omitted and context strings can be added as needed
773
+ * > Multiple context strings may be passed to be joined together
774
+ *
775
+ * @param string (optional) $context Variable number of components to add to context
776
+ * @param bool (optional) $prefix Whether or not to prefix context with request type (public or admin) [Default: TRUE]
777
+ * @return string Context
778
+ */
779
+ function build_context($context = null, $prefix = true) {
780
+ $args = func_get_args();
781
+ //Get prefix option
782
+ if ( !empty($args) ) {
783
+ $prefix = ( is_bool($args[count($args) - 1]) ) ? array_pop($args) : true;
784
+ }
785
+
786
+ //Validate
787
+ $context = array_filter($args, 'is_string');
788
+ $sep = '_';
789
+
790
+ //Context Prefix
791
+ if ( $prefix )
792
+ array_unshift($context, ( is_admin() ) ? 'admin' : 'public' );
793
+ return implode($sep, $context);
794
+ }
795
+
796
+ /**
797
+ * Check if context exists in current request
798
+ * @param string $context Context to check for
799
+ * @return bool TRUE if context exists FALSE otherwise
800
+ */
801
+ function is_context($context) {
802
+ $ret = false;
803
+ if ( is_scalar($context) )
804
+ $context = array($context);
805
+ if ( is_array($context) && !empty($context) ) {
806
+ $ictx = array_intersect($this->get_context(), $context);
807
+ if ( !empty($ictx) )
808
+ $ret = true;
809
+ }
810
+ return $ret;
811
+ }
812
+
813
  /**
814
  * Joins and normalizes the slashes in the paths passed to method
815
  * All forward/back slashes are converted to forward slashes
816
  * Multiple path segments can be passed as additional argments
817
  * @param string $path Path to normalize
818
+ * @param bool|array $trailing_slash (optional) Whether or not normalized path should have a trailing slash or not (Default: FALSE)
819
+ * If array is passed, first index is trailing, second is leading slash
820
+ * If multiple path segments are passed, $trailing_slash will be the LAST parameter (default value used if omitted)
821
  */
822
  function normalize_path($path, $trailing_slash = false) {
823
  $sl_f = '/';
824
  $sl_b = '\\';
825
  $parts = func_get_args();
826
+ //Slash defaults (trailing, leading);
827
+ $slashes = array(false, true);
828
  if ( func_num_args() > 1 ) {
829
+ //Get last argument
830
+ $arg_last = $parts[count($parts) - 1];
831
+ if ( is_bool($arg_last) ) {
832
+ $arg_last = array($arg_last);
833
+ }
834
+
835
+ if ( is_array($arg_last) && count($arg_last) > 0 && is_bool($arg_last[0]) ) {
836
+ //Remove slash paramter from args array
837
  array_pop($parts);
838
+ //Normalize slashes options
839
+ if ( isset($arg_last[0]) )
840
+ $slashes[0] = $arg_last[0];
841
+ if ( isset($arg_last[1]) )
842
+ $slashes[1] = $arg_last[1];
843
  }
844
+ }
845
+ //Extract to slash options local variables
846
+ list($trailing_slash, $leading_slash) = $slashes;
847
+
848
+ //Clean path segments
849
+ foreach ( $parts as $key => $part ) {
850
+ //Trim slashes/spaces
851
+ $parts[$key] = trim($part, " " . $sl_f . $sl_b);
852
+
853
+ //Verify path segment still contains value
854
+ if ( empty($parts[$key]) ) {
855
+ unset($parts[$key]);
856
+ continue;
 
 
 
 
 
 
 
857
  }
858
  }
859
+
860
  //Join path parts together
861
  $parts = implode($sl_b, $parts);
862
  $parts = str_replace($sl_b, $sl_f, $parts);
863
  //Add trailing slash (if necessary)
864
  if ( $trailing_slash )
865
+ $parts .= $sl_f;
866
+ //Add leading slash (if necessary)
867
+ $regex = '#^.+:[\\/]#';
868
+ if ( $leading_slash && !preg_match($regex, $parts) ) {
869
+ $parts = $sl_f . $parts;
870
+ }
871
  return $parts;
872
  }
873
 
874
  /**
875
  * Returns URL of file (assumes that it is in plugin directory)
876
  * @param string $file name of file get URL
877
+ * @return string File URL
878
  */
879
  function get_file_url($file) {
880
+ if ( is_string($file) && '' != trim($file) ) {
881
+ $file = str_replace(' ', '%20', $this->normalize_path($this->get_url_base(), $file));
882
  }
883
  return $file;
884
  }
885
 
886
+ /**
887
+ * Returns path to plugin file
888
+ * @param string $file file name
889
+ * @return string File path
890
+ */
891
+ function get_file_path($file) {
892
+ if ( is_string($file) && '' != trim($file) ) {
893
+ $file = $this->normalize_path($this->get_path_base(), $file);
894
+ }
895
+ return $file;
896
+ }
897
+
898
+ function get_plugin_file_path($file, $trailing_slash = false) {
899
+ if ( is_string($file) && '' != trim($file) )
900
+ $file = $this->normalize_path($this->get_plugin_base(), $file, $trailing_slash);
901
+ return $file;
902
+ }
903
+
904
+ /**
905
+ * Checks if value is valid file name
906
+ * @param string $filename File name to check
907
+ * @return bool TRUE if valid file name, FALSE otherwise
908
+ */
909
+ function is_file($filename) {
910
+ $ext = $this->get_file_extension($filename);
911
+ return ( empty($ext) ) ? false : true;
912
+ }
913
+
914
  /**
915
  * Retrieves file extension
916
  * @param string $file file name/path
917
+ * @param bool (optional) $lowercase Whether lowercase extension should be returned (Default: TRUE)
918
  * @return string File's extension
919
  */
920
+ function get_file_extension($file, $lowercase = true) {
921
  $ret = '';
922
  $sep = '.';
923
+ if ( !is_string($file) )
924
+ return $ret;
925
+ if ( ( $rpos = strrpos($file, $sep) ) > 0 )
926
  $ret = substr($file, $rpos + 1);
927
+ if ( !!$lowercase )
928
+ $ret = strtolower($ret);
929
  return $ret;
930
  }
931
 
932
  /**
933
  * Checks if file has specified extension
934
+ * @uses get_file_extension()
935
  * @param string $file File name/path
936
+ * @param string|array $extension File ending(s) to check $file for
937
+ * @param bool (optional) Whether check should be case senstive or not (Default: FALSE)
938
  * @return bool TRUE if file has extension
939
  */
940
+ function has_file_extension($file, $extension, $case_sensitive = false) {
941
+ if ( !is_array($extension) )
942
+ $extension = array(strval($extension));
943
+ if ( !$case_sensitive ) {
944
+ //Normalize extensions
945
+ $extension = array_map('strtolower', $extension);
946
+ }
947
+ return ( in_array($this->get_file_extension($file, !$case_sensitive), $extension) ) ? true : false;
948
+ }
949
+
950
+ /**
951
+ * Removes file extension from file name
952
+ * The extension is the text following the last period ('.') in the file name
953
+ * @uses get_file_extension()
954
+ * @param string $file File name
955
+ * @return string File name without extension
956
+ */
957
+ function strip_file_extension($file) {
958
+ $ext = $this->get_file_extension($file);
959
+ if ( !empty($ext) ) {
960
+ $file = substr($file, 0, (strlen($ext) + 1) * -1);
961
+ }
962
+ return $file;
963
  }
964
 
965
  /**
966
  * Retrieve base URL for plugin-specific files
967
+ * @uses get_plugin_base()
968
+ * @uses normalize_path()
969
  * @return string Base URL
970
  */
971
  function get_url_base() {
972
  static $url_base = '';
973
  if ( '' == $url_base ) {
974
+ $url_base = $this->normalize_path(plugins_url(), $this->get_plugin_base());
975
  }
976
  return $url_base;
977
  }
978
 
979
+ /**
980
+ * Retrieve plugin's base path
981
+ * @uses WP_PLUGIN_DIR
982
+ * @uses get_plugin_base()
983
+ * @uses normalize_path()
984
+ * @return string Base path
985
+ */
986
  function get_path_base() {
987
  static $path_base = '';
988
  if ( '' == $path_base ) {
990
  }
991
  return $path_base;
992
  }
993
+
994
+ /**
995
+ * Retrieve plugin's base directory
996
+ * @uses WP_PLUGIN_DIR
997
+ * @uses normalize_path()
998
+ * @return string Base directory
999
+ */
1000
+ function get_plugin_base($trim = false) {
1001
  static $plugin_dir = '';
1002
  if ( '' == $plugin_dir ) {
1003
  $plugin_dir = str_replace($this->normalize_path(WP_PLUGIN_DIR), '', $this->normalize_path(dirname(dirname(__FILE__))));
1004
  }
1005
+ if ( $trim )
1006
+ $plugin_dir = trim($plugin_dir, ' \/');
1007
  return $plugin_dir;
1008
  }
1009
 
1010
+ /**
1011
+ * Retrieve plugin's base file path
1012
+ * @uses get_path_base()
1013
+ * @uses get_file_path()
1014
+ * @return string Base file path
1015
+ */
1016
  function get_plugin_base_file() {
1017
+ static $file = '';
1018
+ if ( empty($file) ) {
1019
+ $dir = @ opendir($this->get_path_base());
1020
+ if ( $dir ) {
1021
+ while ( ($ftemp = readdir($dir)) !== false ) {
1022
+ //Only process PHP files
1023
+ $ftemp = $this->get_file_path($ftemp);
1024
+ if ( !$this->has_file_extension($ftemp, 'php') || !is_readable($ftemp) )
1025
+ continue;
1026
+ //Check for data
1027
+ $data = get_file_data($ftemp, $this->plugin_headers);
1028
+ if ( !empty($data['Name']) ) {
1029
+ //Set base file
1030
+ $file = $ftemp;
1031
+ break;
1032
+ }
1033
+ }
1034
+ }
1035
+ @closedir($dir);
1036
+ }
1037
+ //Return
1038
+ return $file;
1039
  }
1040
 
1041
+ /**
1042
+ * Retrieve plugin's internal name
1043
+ * Internal name is used by WP core
1044
+ * @uses get_plugin_base_file()
1045
+ * @uses plugin_basename()
1046
+ * @return string Internal plugin name
1047
+ */
1048
  function get_plugin_base_name() {
1049
+ static $name = false;
1050
+ if ( !$name ) {
1051
+ $file = $this->get_plugin_base_file();
1052
+ $name = plugin_basename($file);
1053
+ }
1054
+ return $name;
1055
+ }
1056
+
1057
+ /**
1058
+ * Retrieve plugin info
1059
+ * Parses info comment in main plugin file
1060
+ * @uses get_plugin_base_file()
1061
+ */
1062
+ function get_plugin_info($field = '') {
1063
+ static $data = array();
1064
+ $ret = '';
1065
+ //Get plugin data
1066
+ if ( empty($data) ) {
1067
+ $file = $this->get_plugin_base_file();
1068
+ $data = get_file_data($file, $this->plugin_headers);
1069
+ }
1070
+ //Return specified field
1071
+ if ( !empty($field) ) {
1072
+ if ( isset($data[$field]) )
1073
+ $ret = $data[$field];
1074
+ } else {
1075
+ $ret = $data;
1076
+ }
1077
+ return $ret;
1078
+ }
1079
+
1080
+ /**
1081
+ * Retrieve plugin version
1082
+ * @uses get_plugin_info()
1083
+ * @param bool $strip_desc Strip any additional version text
1084
+ * @return string Plugin version
1085
+ */
1086
+ function get_plugin_version($strip_desc = true) {
1087
+ static $v = '';
1088
+ //Retrieve version
1089
+ if ( empty($v) ) {
1090
+ $field = 'Version';
1091
+ $v = $this->get_plugin_info($field);
1092
+ }
1093
+ //Format
1094
+ $ret = $v;
1095
+ if ( $strip_desc ) {
1096
+ $ret = explode(' ', $ret, 2);
1097
+ $ret = $ret[0];
1098
+ }
1099
+ //Return
1100
+ return $ret;
1101
+ }
1102
+
1103
+ /**
1104
+ * Retrieve plugin textdomain (for localization)
1105
+ * @return string
1106
+ */
1107
+ function get_plugin_textdomain() {
1108
+ static $dom = '';
1109
+ if ( empty($dom) )
1110
+ $dom = $this->get_plugin_base(true);
1111
+ return $dom;
1112
  }
1113
 
1114
  /**
1149
 
1150
  /*-** General **-*/
1151
 
1152
+ /**
1153
+ * Checks if last parameter sent to a function is an array of options and returns it
1154
+ * Calling function should use `func_get_args()` and pass the value to this method
1155
+ * @param array $args Parameters passed to calling function
1156
+ * @return array Options array (Default: empty array)
1157
+ */
1158
+ function func_get_options($args) {
1159
+ $r = array();
1160
+ if ( is_array($args) && !empty($args) ) {
1161
+ $last = count($args) - 1;
1162
+ if ( is_array($args[$last]) )
1163
+ $r = $args[$last];
1164
+ }
1165
+ return $r;
1166
+ }
1167
+
1168
  /**
1169
  * Checks if a property exists in a class or object
1170
  * (Compatibility method for PHP 4
1203
  }
1204
  }
1205
 
1206
+ /**
1207
+ * Remap array members based on a
1208
+ * mapping of source/destination keys
1209
+ * @param array $properties Associative array of properties
1210
+ * @param array $map Source/Destination mapping
1211
+ * > Key: Source member
1212
+ * > Val: Destination member
1213
+ * @param bool $overwrite If TRUE, source value will be set in destination regardless of whether member already exists or not
1214
+ * @return array Remapped properties
1215
+ */
1216
+ function array_remap($arr, $map = array(), $overwrite = false) {
1217
+ if ( !empty($map) && is_array($map) ) {
1218
+ //Iterate through mappings
1219
+ foreach ( $map as $from => $to ) {
1220
+ if ( !array_key_exists($from, $arr) )
1221
+ continue;
1222
+ $move = $overwrite;
1223
+ //Only remap if parent property doesn't already exist in array
1224
+ if ( !array_key_exists($to, $arr) )
1225
+ $move = true;
1226
+ if ( $move ) {
1227
+ //Move member value to new key
1228
+ $arr[$to] = $arr[$from];
1229
+ //Remove source member
1230
+ unset($arr[$from]);
1231
+ }
1232
+ }
1233
+ }
1234
+ //Return remapped properties
1235
+ return $arr;
1236
+ }
1237
+
1238
+ function array_filter_keys($arr, $keys) {
1239
+ if ( is_array($arr) && !empty($arr) && is_array($keys) && !empty($keys) ) {
1240
+ foreach ( $keys as $rem ) {
1241
+ if ( array_key_exists($rem, $arr) )
1242
+ unset($arr[$rem]);
1243
+ }
1244
+ }
1245
+
1246
+ return $arr;
1247
+ }
1248
+
1249
+ /**
1250
+ * Insert an item into an array at the specified position
1251
+ * @param mixed $item Item to insert into array
1252
+ * @param int $pos Index position to insert item into array
1253
+ * @return array Modified array
1254
+ */
1255
+ function array_insert($array, $item, $pos = null) {
1256
+ array_splice($array, $pos, 0, $item);
1257
+ return $array;
1258
+ }
1259
+
1260
  /**
1261
  * Merges 1 or more arrays together
1262
  * Methodology
1268
  * - Merge item in base array with current item based on key name
1269
  * - If the current item's value AND the corresponding item in the base array are BOTH arrays, recursively merge the the arrays
1270
  * - If the current item's value OR the corresponding item in the base array is NOT an array, current item overwrites base item
 
1271
  * @param array Variable number of arrays
1272
  * @param array $arr1 Default array
1273
  * @return array Merged array
1275
  function array_merge_recursive_distinct($arr1) {
1276
  //Get all arrays passed to function
1277
  $args = func_get_args();
1278
+ if ( empty($args) )
1279
  return false;
1280
+ //Return empty array if first parameter is not an array
1281
+ if ( !is_array($args[0]) )
1282
+ return array();
1283
  //Set first array as base array
1284
  $merged = $args[0];
1285
  //Iterate through arrays to merge
1286
  $arg_length = count($args);
1287
+ for ( $x = 1; $x < $arg_length; $x++ ) {
1288
  //Skip if argument is not an array (only merge arrays)
1289
+ if ( !is_array($args[$x]) )
1290
  continue;
1291
  //Iterate through argument items
1292
+ foreach ( $args[$x] as $key => $val ) {
1293
+ //Generate key for numeric indexes
1294
+ if ( is_int($key) ) {
1295
+ //Add new item to merged array
1296
+ $merged[] = null;
1297
+ //Get key of new item
1298
+ $key = array_pop(array_keys($merged));
1299
+ }
1300
+ if ( !isset($merged[$key]) || !is_array($merged[$key]) || !is_array($val) ) {
1301
+ $merged[$key] = $val;
1302
+ } elseif ( is_array($merged[$key]) && is_array($val) ) {
1303
+ $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $val);
1304
+ }
1305
  }
1306
  }
1307
  return $merged;
1356
  return $item;
1357
  }
1358
 
1359
+ /**
1360
+ * Build formatted string based on array values
1361
+ * Array values in formatted string will be ordered by index order
1362
+ * @param array $attribute Values to build string with
1363
+ * @param string $format (optional) Format name (Default: Multidimensional array representation > ['value1']['value2']['value3'], etc.)
1364
+ * @return string Formatted string based on array values
1365
+ */
1366
  function get_array_path($attribute = '', $format = null) {
1367
  //Formatted value
1368
  $fmtd = '';
1428
  return $path;
1429
  }
1430
 
1431
+ /**
1432
+ * Parse string of attributes into array
1433
+ * For XML/XHTML tag attributes
1434
+ * @param string $txt Attribute text (Can be full tag or just attributes)
1435
+ * @return array Attributes as associative array
1436
+ */
1437
+ function parse_attribute_string($txt, $defaults = array()) {
1438
+ $txt = trim($txt, ' >');
1439
+ $matches = $attr = array();
1440
+ //Strip tag
1441
+ if ( $txt[0] == '<' && ($s = strpos($txt, ' ')) && $s !== false ) {
1442
+ $txt = trim(substr($txt, $s + 1));
1443
+ }
1444
+ //Parse attributes
1445
+ $rgx = "/\b(\w+.*?)=([\"'])(.*?)\\2(?:\s|$)/i";
1446
+ preg_match_all($rgx, $txt, $matches);
1447
+ if ( count($matches) > 3 ) {
1448
+ foreach ( $matches[1] as $sub_idx => $val ) {
1449
+ if ( isset($matches[3][$sub_idx]) )
1450
+ $attr[trim($val)] = trim($matches[3][$sub_idx]);
1451
+ }
1452
+ }
1453
+ //Destroy parsing vars
1454
+ unset($txt, $matches);
1455
+
1456
+ return array_merge($defaults, $attr);
1457
+ }
1458
+
1459
  /**
1460
  * Builds attribute string for HTML element
1461
  * @param array $attr Attributes
1476
  return $ret;
1477
  }
1478
 
1479
+ function build_html_link($uri, $content, $attributes = array()) {
1480
+ $attributes = array_merge(array('href' => $uri, 'title' => $content), $attributes);
1481
+ return $this->build_html_element(array('tag' => 'a', 'wrap' => true, 'content' => $content, 'attributes' => $attributes));
1482
+ }
1483
+
1484
  /**
1485
  * Generate external stylesheet element
1486
  * @param $url Stylesheet URL
1491
  return $this->build_html_element(array('tag' => 'link', 'wrap' => false, 'attributes' => $attributes));
1492
  }
1493
 
1494
+ function build_script_element($content = '', $id = '', $wrap_jquery = true, $wait_doc_ready = false) {
1495
+ //Stop processing invalid content
1496
+ if ( empty($content) || !is_string($content) )
1497
+ return '';
1498
+ $attributes = array('type' => 'text/javascript');
1499
+ $start = array('/* <![CDATA[ */');
1500
+ $end = array('/* ]]> */');
1501
+ if ( $wrap_jquery ) {
1502
+ $start[] = '(function($){';
1503
+ $end[] = '})(jQuery);';
1504
+
1505
+ //Add event handler (if necessary)
1506
+ if ( $wait_doc_ready ) {
1507
+ $start[] = '$(document).ready(function(){';
1508
+ $end[] = '})';
1509
+ }
1510
+ }
1511
+
1512
+ //Reverse order of end values
1513
+ $end = array_reverse($end);
1514
+ $content = implode('', array_merge($start, array($content), $end));
1515
+ if ( is_string($id) && !empty($id) ) {
1516
+ $attributes['id'] = $this->add_prefix($id);
1517
+ }
1518
+ return $this->build_html_element(array('tag' => 'script', 'content' => $content, 'attributes' => $attributes)) . PHP_EOL;
1519
+ }
1520
+
1521
  /**
1522
  * Generate external script element
1523
  * @param $url Script URL
1590
  //Get last submenu added
1591
  $parent = $this->get_submenu_parent_file($parent);
1592
  if ( isset($submenu[$parent]) ) {
1593
+ $subs =& $submenu[$parent];
1594
+
1595
+ //Make sure menu isn't already in the desired position
1596
+ if ( $pos <= ( count($subs) - 1 ) ) {
1597
+ //Get submenu that was just added
1598
+ $sub = array_pop($subs);
1599
+ //Insert into desired position
1600
+ if ( 0 == $pos ) {
1601
+ array_unshift($subs, $sub);
1602
+ } else {
1603
+ $top = array_slice($subs, 0, $pos);
1604
+ $bottom = array_slice($subs, $pos);
1605
+ array_push($top, $sub);
1606
+ $subs = array_merge($top, $bottom);
1607
  }
1608
  }
1609
  }
1610
+ }
1611
 
1612
  return $hookname;
1613
  }
js/dev/effects.js DELETED
@@ -1,1094 +0,0 @@
1
- // script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
-
3
- // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
- // Contributors:
5
- // Justin Palmer (http://encytemedia.com/)
6
- // Mark Pilgrim (http://diveintomark.org/)
7
- // Martin Bialasinki
8
- //
9
- // script.aculo.us is freely distributable under the terms of an MIT-style license.
10
- // For details, see the script.aculo.us web site: http://script.aculo.us/
11
-
12
- // converts rgb() and #xxx to #xxxxxx format,
13
- // returns self (or first argument) if not convertable
14
- String.prototype.parseColor = function() {
15
- var color = '#';
16
- if(this.slice(0,4) == 'rgb(') {
17
- var cols = this.slice(4,this.length-1).split(',');
18
- var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
19
- } else {
20
- if(this.slice(0,1) == '#') {
21
- if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
22
- if(this.length==7) color = this.toLowerCase();
23
- }
24
- }
25
- return(color.length==7 ? color : (arguments[0] || this));
26
- }
27
-
28
- /*--------------------------------------------------------------------------*/
29
-
30
- Element.collectTextNodes = function(element) {
31
- return $A($(element).childNodes).collect( function(node) {
32
- return (node.nodeType==3 ? node.nodeValue :
33
- (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
34
- }).flatten().join('');
35
- }
36
-
37
- Element.collectTextNodesIgnoreClass = function(element, className) {
38
- return $A($(element).childNodes).collect( function(node) {
39
- return (node.nodeType==3 ? node.nodeValue :
40
- ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
41
- Element.collectTextNodesIgnoreClass(node, className) : ''));
42
- }).flatten().join('');
43
- }
44
-
45
- Element.setContentZoom = function(element, percent) {
46
- element = $(element);
47
- element.setStyle({fontSize: (percent/100) + 'em'});
48
- if(Prototype.Browser.WebKit) window.scrollBy(0,0);
49
- return element;
50
- }
51
-
52
- Element.getInlineOpacity = function(element){
53
- return $(element).style.opacity || '';
54
- }
55
-
56
- Element.forceRerendering = function(element) {
57
- try {
58
- element = $(element);
59
- var n = document.createTextNode(' ');
60
- element.appendChild(n);
61
- element.removeChild(n);
62
- } catch(e) { }
63
- };
64
-
65
- /*--------------------------------------------------------------------------*/
66
-
67
- Array.prototype.call = function() {
68
- var args = arguments;
69
- this.each(function(f){ f.apply(this, args) });
70
- }
71
-
72
- /*--------------------------------------------------------------------------*/
73
-
74
- var Effect = {
75
- _elementDoesNotExistError: {
76
- name: 'ElementDoesNotExistError',
77
- message: 'The specified DOM element does not exist, but is required for this effect to operate'
78
- },
79
- tagifyText: function(element) {
80
- if(typeof Builder == 'undefined')
81
- throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
82
-
83
- var tagifyStyle = 'position:relative';
84
- if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
85
-
86
- element = $(element);
87
- $A(element.childNodes).each( function(child) {
88
- if(child.nodeType==3) {
89
- child.nodeValue.toArray().each( function(character) {
90
- element.insertBefore(
91
- Builder.node('span',{style: tagifyStyle},
92
- character == ' ' ? String.fromCharCode(160) : character),
93
- child);
94
- });
95
- Element.remove(child);
96
- }
97
- });
98
- },
99
- multiple: function(element, effect) {
100
- var elements;
101
- if(((typeof element == 'object') ||
102
- (typeof element == 'function')) &&
103
- (element.length))
104
- elements = element;
105
- else
106
- elements = $(element).childNodes;
107
-
108
- var options = Object.extend({
109
- speed: 0.1,
110
- delay: 0.0
111
- }, arguments[2] || {});
112
- var masterDelay = options.delay;
113
-
114
- $A(elements).each( function(element, index) {
115
- new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
116
- });
117
- },
118
- PAIRS: {
119
- 'slide': ['SlideDown','SlideUp'],
120
- 'blind': ['BlindDown','BlindUp'],
121
- 'appear': ['Appear','Fade']
122
- },
123
- toggle: function(element, effect) {
124
- element = $(element);
125
- effect = (effect || 'appear').toLowerCase();
126
- var options = Object.extend({
127
- queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
128
- }, arguments[2] || {});
129
- Effect[element.visible() ?
130
- Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
131
- }
132
- };
133
-
134
- var Effect2 = Effect; // deprecated
135
-
136
- /* ------------- transitions ------------- */
137
-
138
- Effect.Transitions = {
139
- linear: Prototype.K,
140
- sinoidal: function(pos) {
141
- return (-Math.cos(pos*Math.PI)/2) + 0.5;
142
- },
143
- reverse: function(pos) {
144
- return 1-pos;
145
- },
146
- flicker: function(pos) {
147
- var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
148
- return (pos > 1 ? 1 : pos);
149
- },
150
- wobble: function(pos) {
151
- return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
152
- },
153
- pulse: function(pos, pulses) {
154
- pulses = pulses || 5;
155
- return (
156
- Math.round((pos % (1/pulses)) * pulses) == 0 ?
157
- ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
158
- 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
159
- );
160
- },
161
- none: function(pos) {
162
- return 0;
163
- },
164
- full: function(pos) {
165
- return 1;
166
- }
167
- };
168
-
169
- /* ------------- core effects ------------- */
170
-
171
- Effect.ScopedQueue = Class.create();
172
- Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
173
- initialize: function() {
174
- this.effects = [];
175
- this.interval = null;
176
- },
177
- _each: function(iterator) {
178
- this.effects._each(iterator);
179
- },
180
- add: function(effect) {
181
- var timestamp = new Date().getTime();
182
-
183
- var position = (typeof effect.options.queue == 'string') ?
184
- effect.options.queue : effect.options.queue.position;
185
-
186
- switch(position) {
187
- case 'front':
188
- // move unstarted effects after this effect
189
- this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
190
- e.startOn += effect.finishOn;
191
- e.finishOn += effect.finishOn;
192
- });
193
- break;
194
- case 'with-last':
195
- timestamp = this.effects.pluck('startOn').max() || timestamp;
196
- break;
197
- case 'end':
198
- // start effect after last queued effect has finished
199
- timestamp = this.effects.pluck('finishOn').max() || timestamp;
200
- break;
201
- }
202
-
203
- effect.startOn += timestamp;
204
- effect.finishOn += timestamp;
205
-
206
- if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
207
- this.effects.push(effect);
208
-
209
- if(!this.interval)
210
- this.interval = setInterval(this.loop.bind(this), 15);
211
- },
212
- remove: function(effect) {
213
- this.effects = this.effects.reject(function(e) { return e==effect });
214
- if(this.effects.length == 0) {
215
- clearInterval(this.interval);
216
- this.interval = null;
217
- }
218
- },
219
- loop: function() {
220
- var timePos = new Date().getTime();
221
- for(var i=0, len=this.effects.length;i<len;i++)
222
- this.effects[i] && this.effects[i].loop(timePos);
223
- }
224
- });
225
-
226
- Effect.Queues = {
227
- instances: $H(),
228
- get: function(queueName) {
229
- if(typeof queueName != 'string') return queueName;
230
-
231
- if(!this.instances[queueName])
232
- this.instances[queueName] = new Effect.ScopedQueue();
233
-
234
- return this.instances[queueName];
235
- }
236
- }
237
- Effect.Queue = Effect.Queues.get('global');
238
-
239
- Effect.DefaultOptions = {
240
- transition: Effect.Transitions.sinoidal,
241
- duration: 1.0, // seconds
242
- fps: 100, // 100= assume 66fps max.
243
- sync: false, // true for combining
244
- from: 0.0,
245
- to: 1.0,
246
- delay: 0.0,
247
- queue: 'parallel'
248
- }
249
-
250
- Effect.Base = function() {};
251
- Effect.Base.prototype = {
252
- position: null,
253
- start: function(options) {
254
- function codeForEvent(options,eventName){
255
- return (
256
- (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
257
- (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
258
- );
259
- }
260
- if(options.transition === false) options.transition = Effect.Transitions.linear;
261
- this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
262
- this.currentFrame = 0;
263
- this.state = 'idle';
264
- this.startOn = this.options.delay*1000;
265
- this.finishOn = this.startOn+(this.options.duration*1000);
266
- this.fromToDelta = this.options.to-this.options.from;
267
- this.totalTime = this.finishOn-this.startOn;
268
- this.totalFrames = this.options.fps*this.options.duration;
269
-
270
- eval('this.render = function(pos){ '+
271
- 'if(this.state=="idle"){this.state="running";'+
272
- codeForEvent(options,'beforeSetup')+
273
- (this.setup ? 'this.setup();':'')+
274
- codeForEvent(options,'afterSetup')+
275
- '};if(this.state=="running"){'+
276
- 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
277
- 'this.position=pos;'+
278
- codeForEvent(options,'beforeUpdate')+
279
- (this.update ? 'this.update(pos);':'')+
280
- codeForEvent(options,'afterUpdate')+
281
- '}}');
282
-
283
- this.event('beforeStart');
284
- if(!this.options.sync)
285
- Effect.Queues.get(typeof this.options.queue == 'string' ?
286
- 'global' : this.options.queue.scope).add(this);
287
- },
288
- loop: function(timePos) {
289
- if(timePos >= this.startOn) {
290
- if(timePos >= this.finishOn) {
291
- this.render(1.0);
292
- this.cancel();
293
- this.event('beforeFinish');
294
- if(this.finish) this.finish();
295
- this.event('afterFinish');
296
- return;
297
- }
298
- var pos = (timePos - this.startOn) / this.totalTime,
299
- frame = Math.round(pos * this.totalFrames);
300
- if(frame > this.currentFrame) {
301
- this.render(pos);
302
- this.currentFrame = frame;
303
- }
304
- }
305
- },
306
- cancel: function() {
307
- if(!this.options.sync)
308
- Effect.Queues.get(typeof this.options.queue == 'string' ?
309
- 'global' : this.options.queue.scope).remove(this);
310
- this.state = 'finished';
311
- },
312
- event: function(eventName) {
313
- if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
314
- if(this.options[eventName]) this.options[eventName](this);
315
- },
316
- inspect: function() {
317
- var data = $H();
318
- for(property in this)
319
- if(typeof this[property] != 'function') data[property] = this[property];
320
- return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
321
- }
322
- }
323
-
324
- Effect.Parallel = Class.create();
325
- Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
326
- initialize: function(effects) {
327
- this.effects = effects || [];
328
- this.start(arguments[1]);
329
- },
330
- update: function(position) {
331
- this.effects.invoke('render', position);
332
- },
333
- finish: function(position) {
334
- this.effects.each( function(effect) {
335
- effect.render(1.0);
336
- effect.cancel();
337
- effect.event('beforeFinish');
338
- if(effect.finish) effect.finish(position);
339
- effect.event('afterFinish');
340
- });
341
- }
342
- });
343
-
344
- Effect.Event = Class.create();
345
- Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
346
- initialize: function() {
347
- var options = Object.extend({
348
- duration: 0
349
- }, arguments[0] || {});
350
- this.start(options);
351
- },
352
- update: Prototype.emptyFunction
353
- });
354
-
355
- Effect.Opacity = Class.create();
356
- Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
357
- initialize: function(element) {
358
- this.element = $(element);
359
- if(!this.element) throw(Effect._elementDoesNotExistError);
360
- // make this work on IE on elements without 'layout'
361
- if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
362
- this.element.setStyle({zoom: 1});
363
- var options = Object.extend({
364
- from: this.element.getOpacity() || 0.0,
365
- to: 1.0
366
- }, arguments[1] || {});
367
- this.start(options);
368
- },
369
- update: function(position) {
370
- this.element.setOpacity(position);
371
- }
372
- });
373
-
374
- Effect.Move = Class.create();
375
- Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
376
- initialize: function(element) {
377
- this.element = $(element);
378
- if(!this.element) throw(Effect._elementDoesNotExistError);
379
- var options = Object.extend({
380
- x: 0,
381
- y: 0,
382
- mode: 'relative'
383
- }, arguments[1] || {});
384
- this.start(options);
385
- },
386
- setup: function() {
387
- // Bug in Opera: Opera returns the "real" position of a static element or
388
- // relative element that does not have top/left explicitly set.
389
- // ==> Always set top and left for position relative elements in your stylesheets
390
- // (to 0 if you do not need them)
391
- this.element.makePositioned();
392
- this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
393
- this.originalTop = parseFloat(this.element.getStyle('top') || '0');
394
- if(this.options.mode == 'absolute') {
395
- // absolute movement, so we need to calc deltaX and deltaY
396
- this.options.x = this.options.x - this.originalLeft;
397
- this.options.y = this.options.y - this.originalTop;
398
- }
399
- },
400
- update: function(position) {
401
- this.element.setStyle({
402
- left: Math.round(this.options.x * position + this.originalLeft) + 'px',
403
- top: Math.round(this.options.y * position + this.originalTop) + 'px'
404
- });
405
- }
406
- });
407
-
408
- // for backwards compatibility
409
- Effect.MoveBy = function(element, toTop, toLeft) {
410
- return new Effect.Move(element,
411
- Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
412
- };
413
-
414
- Effect.Scale = Class.create();
415
- Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
416
- initialize: function(element, percent) {
417
- this.element = $(element);
418
- if(!this.element) throw(Effect._elementDoesNotExistError);
419
- var options = Object.extend({
420
- scaleX: true,
421
- scaleY: true,
422
- scaleContent: true,
423
- scaleFromCenter: false,
424
- scaleMode: 'box', // 'box' or 'contents' or {} with provided values
425
- scaleFrom: 100.0,
426
- scaleTo: percent
427
- }, arguments[2] || {});
428
- this.start(options);
429
- },
430
- setup: function() {
431
- this.restoreAfterFinish = this.options.restoreAfterFinish || false;
432
- this.elementPositioning = this.element.getStyle('position');
433
-
434
- this.originalStyle = {};
435
- ['top','left','width','height','fontSize'].each( function(k) {
436
- this.originalStyle[k] = this.element.style[k];
437
- }.bind(this));
438
-
439
- this.originalTop = this.element.offsetTop;
440
- this.originalLeft = this.element.offsetLeft;
441
-
442
- var fontSize = this.element.getStyle('font-size') || '100%';
443
- ['em','px','%','pt'].each( function(fontSizeType) {
444
- if(fontSize.indexOf(fontSizeType)>0) {
445
- this.fontSize = parseFloat(fontSize);
446
- this.fontSizeType = fontSizeType;
447
- }
448
- }.bind(this));
449
-
450
- this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
451
-
452
- this.dims = null;
453
- if(this.options.scaleMode=='box')
454
- this.dims = [this.element.offsetHeight, this.element.offsetWidth];
455
- if(/^content/.test(this.options.scaleMode))
456
- this.dims = [this.element.scrollHeight, this.element.scrollWidth];
457
- if(!this.dims)
458
- this.dims = [this.options.scaleMode.originalHeight,
459
- this.options.scaleMode.originalWidth];
460
- },
461
- update: function(position) {
462
- var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
463
- if(this.options.scaleContent && this.fontSize)
464
- this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
465
- this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
466
- },
467
- finish: function(position) {
468
- if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
469
- },
470
- setDimensions: function(height, width) {
471
- var d = {};
472
- if(this.options.scaleX) d.width = Math.round(width) + 'px';
473
- if(this.options.scaleY) d.height = Math.round(height) + 'px';
474
- if(this.options.scaleFromCenter) {
475
- var topd = (height - this.dims[0])/2;
476
- var leftd = (width - this.dims[1])/2;
477
- if(this.elementPositioning == 'absolute') {
478
- if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
479
- if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
480
- } else {
481
- if(this.options.scaleY) d.top = -topd + 'px';
482
- if(this.options.scaleX) d.left = -leftd + 'px';
483
- }
484
- }
485
- this.element.setStyle(d);
486
- }
487
- });
488
-
489
- Effect.Highlight = Class.create();
490
- Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
491
- initialize: function(element) {
492
- this.element = $(element);
493
- if(!this.element) throw(Effect._elementDoesNotExistError);
494
- var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
495
- this.start(options);
496
- },
497
- setup: function() {
498
- // Prevent executing on elements not in the layout flow
499
- if(this.element.getStyle('display')=='none') { this.cancel(); return; }
500
- // Disable background image during the effect
501
- this.oldStyle = {};
502
- if (!this.options.keepBackgroundImage) {
503
- this.oldStyle.backgroundImage = this.element.getStyle('background-image');
504
- this.element.setStyle({backgroundImage: 'none'});
505
- }
506
- if(!this.options.endcolor)
507
- this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
508
- if(!this.options.restorecolor)
509
- this.options.restorecolor = this.element.getStyle('background-color');
510
- // init color calculations
511
- this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
512
- this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
513
- },
514
- update: function(position) {
515
- this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
516
- return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
517
- },
518
- finish: function() {
519
- this.element.setStyle(Object.extend(this.oldStyle, {
520
- backgroundColor: this.options.restorecolor
521
- }));
522
- }
523
- });
524
-
525
- Effect.ScrollTo = Class.create();
526
- Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
527
- initialize: function(element) {
528
- this.element = $(element);
529
- this.start(arguments[1] || {});
530
- },
531
- setup: function() {
532
- Position.prepare();
533
- var offsets = Position.cumulativeOffset(this.element);
534
- if(this.options.offset) offsets[1] += this.options.offset;
535
- var max = window.innerHeight ?
536
- window.height - window.innerHeight :
537
- document.body.scrollHeight -
538
- (document.documentElement.clientHeight ?
539
- document.documentElement.clientHeight : document.body.clientHeight);
540
- this.scrollStart = Position.deltaY;
541
- this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
542
- },
543
- update: function(position) {
544
- Position.prepare();
545
- window.scrollTo(Position.deltaX,
546
- this.scrollStart + (position*this.delta));
547
- }
548
- });
549
-
550
- /* ------------- combination effects ------------- */
551
-
552
- Effect.Fade = function(element) {
553
- element = $(element);
554
- var oldOpacity = element.getInlineOpacity();
555
- var options = Object.extend({
556
- from: element.getOpacity() || 1.0,
557
- to: 0.0,
558
- afterFinishInternal: function(effect) {
559
- if(effect.options.to!=0) return;
560
- effect.element.hide().setStyle({opacity: oldOpacity});
561
- }}, arguments[1] || {});
562
- return new Effect.Opacity(element,options);
563
- }
564
-
565
- Effect.Appear = function(element) {
566
- element = $(element);
567
- var options = Object.extend({
568
- from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
569
- to: 1.0,
570
- // force Safari to render floated elements properly
571
- afterFinishInternal: function(effect) {
572
- effect.element.forceRerendering();
573
- },
574
- beforeSetup: function(effect) {
575
- effect.element.setOpacity(effect.options.from).show();
576
- }}, arguments[1] || {});
577
- return new Effect.Opacity(element,options);
578
- }
579
-
580
- Effect.Puff = function(element) {
581
- element = $(element);
582
- var oldStyle = {
583
- opacity: element.getInlineOpacity(),
584
- position: element.getStyle('position'),
585
- top: element.style.top,
586
- left: element.style.left,
587
- width: element.style.width,
588
- height: element.style.height
589
- };
590
- return new Effect.Parallel(
591
- [ new Effect.Scale(element, 200,
592
- { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
593
- new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
594
- Object.extend({ duration: 1.0,
595
- beforeSetupInternal: function(effect) {
596
- Position.absolutize(effect.effects[0].element)
597
- },
598
- afterFinishInternal: function(effect) {
599
- effect.effects[0].element.hide().setStyle(oldStyle); }
600
- }, arguments[1] || {})
601
- );
602
- }
603
-
604
- Effect.BlindUp = function(element) {
605
- element = $(element);
606
- element.makeClipping();
607
- return new Effect.Scale(element, 0,
608
- Object.extend({ scaleContent: false,
609
- scaleX: false,
610
- restoreAfterFinish: true,
611
- afterFinishInternal: function(effect) {
612
- effect.element.hide().undoClipping();
613
- }
614
- }, arguments[1] || {})
615
- );
616
- }
617
-
618
- Effect.BlindDown = function(element) {
619
- element = $(element);
620
- var elementDimensions = element.getDimensions();
621
- return new Effect.Scale(element, 100, Object.extend({
622
- scaleContent: false,
623
- scaleX: false,
624
- scaleFrom: 0,
625
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
626
- restoreAfterFinish: true,
627
- afterSetup: function(effect) {
628
- effect.element.makeClipping().setStyle({height: '0px'}).show();
629
- },
630
- afterFinishInternal: function(effect) {
631
- effect.element.undoClipping();
632
- }
633
- }, arguments[1] || {}));
634
- }
635
-
636
- Effect.SwitchOff = function(element) {
637
- element = $(element);
638
- var oldOpacity = element.getInlineOpacity();
639
- return new Effect.Appear(element, Object.extend({
640
- duration: 0.4,
641
- from: 0,
642
- transition: Effect.Transitions.flicker,
643
- afterFinishInternal: function(effect) {
644
- new Effect.Scale(effect.element, 1, {
645
- duration: 0.3, scaleFromCenter: true,
646
- scaleX: false, scaleContent: false, restoreAfterFinish: true,
647
- beforeSetup: function(effect) {
648
- effect.element.makePositioned().makeClipping();
649
- },
650
- afterFinishInternal: function(effect) {
651
- effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
652
- }
653
- })
654
- }
655
- }, arguments[1] || {}));
656
- }
657
-
658
- Effect.DropOut = function(element) {
659
- element = $(element);
660
- var oldStyle = {
661
- top: element.getStyle('top'),
662
- left: element.getStyle('left'),
663
- opacity: element.getInlineOpacity() };
664
- return new Effect.Parallel(
665
- [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
666
- new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
667
- Object.extend(
668
- { duration: 0.5,
669
- beforeSetup: function(effect) {
670
- effect.effects[0].element.makePositioned();
671
- },
672
- afterFinishInternal: function(effect) {
673
- effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
674
- }
675
- }, arguments[1] || {}));
676
- }
677
-
678
- Effect.Shake = function(element) {
679
- element = $(element);
680
- var oldStyle = {
681
- top: element.getStyle('top'),
682
- left: element.getStyle('left') };
683
- return new Effect.Move(element,
684
- { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
685
- new Effect.Move(effect.element,
686
- { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
687
- new Effect.Move(effect.element,
688
- { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
689
- new Effect.Move(effect.element,
690
- { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
691
- new Effect.Move(effect.element,
692
- { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
693
- new Effect.Move(effect.element,
694
- { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
695
- effect.element.undoPositioned().setStyle(oldStyle);
696
- }}) }}) }}) }}) }}) }});
697
- }
698
-
699
- Effect.SlideDown = function(element) {
700
- element = $(element).cleanWhitespace();
701
- // SlideDown need to have the content of the element wrapped in a container element with fixed height!
702
- var oldInnerBottom = element.down().getStyle('bottom');
703
- var elementDimensions = element.getDimensions();
704
- return new Effect.Scale(element, 100, Object.extend({
705
- scaleContent: false,
706
- scaleX: false,
707
- scaleFrom: window.opera ? 0 : 1,
708
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
709
- restoreAfterFinish: true,
710
- afterSetup: function(effect) {
711
- effect.element.makePositioned();
712
- effect.element.down().makePositioned();
713
- if(window.opera) effect.element.setStyle({top: ''});
714
- effect.element.makeClipping().setStyle({height: '0px'}).show();
715
- },
716
- afterUpdateInternal: function(effect) {
717
- effect.element.down().setStyle({bottom:
718
- (effect.dims[0] - effect.element.clientHeight) + 'px' });
719
- },
720
- afterFinishInternal: function(effect) {
721
- effect.element.undoClipping().undoPositioned();
722
- effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
723
- }, arguments[1] || {})
724
- );
725
- }
726
-
727
- Effect.SlideUp = function(element) {
728
- element = $(element).cleanWhitespace();
729
- var oldInnerBottom = element.down().getStyle('bottom');
730
- return new Effect.Scale(element, window.opera ? 0 : 1,
731
- Object.extend({ scaleContent: false,
732
- scaleX: false,
733
- scaleMode: 'box',
734
- scaleFrom: 100,
735
- restoreAfterFinish: true,
736
- beforeStartInternal: function(effect) {
737
- effect.element.makePositioned();
738
- effect.element.down().makePositioned();
739
- if(window.opera) effect.element.setStyle({top: ''});
740
- effect.element.makeClipping().show();
741
- },
742
- afterUpdateInternal: function(effect) {
743
- effect.element.down().setStyle({bottom:
744
- (effect.dims[0] - effect.element.clientHeight) + 'px' });
745
- },
746
- afterFinishInternal: function(effect) {
747
- effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
748
- effect.element.down().undoPositioned();
749
- }
750
- }, arguments[1] || {})
751
- );
752
- }
753
-
754
- // Bug in opera makes the TD containing this element expand for a instance after finish
755
- Effect.Squish = function(element) {
756
- return new Effect.Scale(element, window.opera ? 1 : 0, {
757
- restoreAfterFinish: true,
758
- beforeSetup: function(effect) {
759
- effect.element.makeClipping();
760
- },
761
- afterFinishInternal: function(effect) {
762
- effect.element.hide().undoClipping();
763
- }
764
- });
765
- }
766
-
767
- Effect.Grow = function(element) {
768
- element = $(element);
769
- var options = Object.extend({
770
- direction: 'center',
771
- moveTransition: Effect.Transitions.sinoidal,
772
- scaleTransition: Effect.Transitions.sinoidal,
773
- opacityTransition: Effect.Transitions.full
774
- }, arguments[1] || {});
775
- var oldStyle = {
776
- top: element.style.top,
777
- left: element.style.left,
778
- height: element.style.height,
779
- width: element.style.width,
780
- opacity: element.getInlineOpacity() };
781
-
782
- var dims = element.getDimensions();
783
- var initialMoveX, initialMoveY;
784
- var moveX, moveY;
785
-
786
- switch (options.direction) {
787
- case 'top-left':
788
- initialMoveX = initialMoveY = moveX = moveY = 0;
789
- break;
790
- case 'top-right':
791
- initialMoveX = dims.width;
792
- initialMoveY = moveY = 0;
793
- moveX = -dims.width;
794
- break;
795
- case 'bottom-left':
796
- initialMoveX = moveX = 0;
797
- initialMoveY = dims.height;
798
- moveY = -dims.height;
799
- break;
800
- case 'bottom-right':
801
- initialMoveX = dims.width;
802
- initialMoveY = dims.height;
803
- moveX = -dims.width;
804
- moveY = -dims.height;
805
- break;
806
- case 'center':
807
- initialMoveX = dims.width / 2;
808
- initialMoveY = dims.height / 2;
809
- moveX = -dims.width / 2;
810
- moveY = -dims.height / 2;
811
- break;
812
- }
813
-
814
- return new Effect.Move(element, {
815
- x: initialMoveX,
816
- y: initialMoveY,
817
- duration: 0.01,
818
- beforeSetup: function(effect) {
819
- effect.element.hide().makeClipping().makePositioned();
820
- },
821
- afterFinishInternal: function(effect) {
822
- new Effect.Parallel(
823
- [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
824
- new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
825
- new Effect.Scale(effect.element, 100, {
826
- scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
827
- sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
828
- ], Object.extend({
829
- beforeSetup: function(effect) {
830
- effect.effects[0].element.setStyle({height: '0px'}).show();
831
- },
832
- afterFinishInternal: function(effect) {
833
- effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
834
- }
835
- }, options)
836
- )
837
- }
838
- });
839
- }
840
-
841
- Effect.Shrink = function(element) {
842
- element = $(element);
843
- var options = Object.extend({
844
- direction: 'center',
845
- moveTransition: Effect.Transitions.sinoidal,
846
- scaleTransition: Effect.Transitions.sinoidal,
847
- opacityTransition: Effect.Transitions.none
848
- }, arguments[1] || {});
849
- var oldStyle = {
850
- top: element.style.top,
851
- left: element.style.left,
852
- height: element.style.height,
853
- width: element.style.width,
854
- opacity: element.getInlineOpacity() };
855
-
856
- var dims = element.getDimensions();
857
- var moveX, moveY;
858
-
859
- switch (options.direction) {
860
- case 'top-left':
861
- moveX = moveY = 0;
862
- break;
863
- case 'top-right':
864
- moveX = dims.width;
865
- moveY = 0;
866
- break;
867
- case 'bottom-left':
868
- moveX = 0;
869
- moveY = dims.height;
870
- break;
871
- case 'bottom-right':
872
- moveX = dims.width;
873
- moveY = dims.height;
874
- break;
875
- case 'center':
876
- moveX = dims.width / 2;
877
- moveY = dims.height / 2;
878
- break;
879
- }
880
-
881
- return new Effect.Parallel(
882
- [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
883
- new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
884
- new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
885
- ], Object.extend({
886
- beforeStartInternal: function(effect) {
887
- effect.effects[0].element.makePositioned().makeClipping();
888
- },
889
- afterFinishInternal: function(effect) {
890
- effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
891
- }, options)
892
- );
893
- }
894
-
895
- Effect.Pulsate = function(element) {
896
- element = $(element);
897
- var options = arguments[1] || {};
898
- var oldOpacity = element.getInlineOpacity();
899
- var transition = options.transition || Effect.Transitions.sinoidal;
900
- var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
901
- reverser.bind(transition);
902
- return new Effect.Opacity(element,
903
- Object.extend(Object.extend({ duration: 2.0, from: 0,
904
- afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
905
- }, options), {transition: reverser}));
906
- }
907
-
908
- Effect.Fold = function(element) {
909
- element = $(element);
910
- var oldStyle = {
911
- top: element.style.top,
912
- left: element.style.left,
913
- width: element.style.width,
914
- height: element.style.height };
915
- element.makeClipping();
916
- return new Effect.Scale(element, 5, Object.extend({
917
- scaleContent: false,
918
- scaleX: false,
919
- afterFinishInternal: function(effect) {
920
- new Effect.Scale(element, 1, {
921
- scaleContent: false,
922
- scaleY: false,
923
- afterFinishInternal: function(effect) {
924
- effect.element.hide().undoClipping().setStyle(oldStyle);
925
- } });
926
- }}, arguments[1] || {}));
927
- };
928
-
929
- Effect.Morph = Class.create();
930
- Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
931
- initialize: function(element) {
932
- this.element = $(element);
933
- if(!this.element) throw(Effect._elementDoesNotExistError);
934
- var options = Object.extend({
935
- style: {}
936
- }, arguments[1] || {});
937
- if (typeof options.style == 'string') {
938
- if(options.style.indexOf(':') == -1) {
939
- var cssText = '', selector = '.' + options.style;
940
- $A(document.styleSheets).reverse().each(function(styleSheet) {
941
- if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
942
- else if (styleSheet.rules) cssRules = styleSheet.rules;
943
- $A(cssRules).reverse().each(function(rule) {
944
- if (selector == rule.selectorText) {
945
- cssText = rule.style.cssText;
946
- throw $break;
947
- }
948
- });
949
- if (cssText) throw $break;
950
- });
951
- this.style = cssText.parseStyle();
952
- options.afterFinishInternal = function(effect){
953
- effect.element.addClassName(effect.options.style);
954
- effect.transforms.each(function(transform) {
955
- if(transform.style != 'opacity')
956
- effect.element.style[transform.style] = '';
957
- });
958
- }
959
- } else this.style = options.style.parseStyle();
960
- } else this.style = $H(options.style)
961
- this.start(options);
962
- },
963
- setup: function(){
964
- function parseColor(color){
965
- if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
966
- color = color.parseColor();
967
- return $R(0,2).map(function(i){
968
- return parseInt( color.slice(i*2+1,i*2+3), 16 )
969
- });
970
- }
971
- this.transforms = this.style.map(function(pair){
972
- var property = pair[0], value = pair[1], unit = null;
973
-
974
- if(value.parseColor('#zzzzzz') != '#zzzzzz') {
975
- value = value.parseColor();
976
- unit = 'color';
977
- } else if(property == 'opacity') {
978
- value = parseFloat(value);
979
- if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
980
- this.element.setStyle({zoom: 1});
981
- } else if(Element.CSS_LENGTH.test(value)) {
982
- var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
983
- value = parseFloat(components[1]);
984
- unit = (components.length == 3) ? components[2] : null;
985
- }
986
-
987
- var originalValue = this.element.getStyle(property);
988
- return {
989
- style: property.camelize(),
990
- originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
991
- targetValue: unit=='color' ? parseColor(value) : value,
992
- unit: unit
993
- };
994
- }.bind(this)).reject(function(transform){
995
- return (
996
- (transform.originalValue == transform.targetValue) ||
997
- (
998
- transform.unit != 'color' &&
999
- (isNaN(transform.originalValue) || isNaN(transform.targetValue))
1000
- )
1001
- )
1002
- });
1003
- },
1004
- update: function(position) {
1005
- var style = {}, transform, i = this.transforms.length;
1006
- while(i--)
1007
- style[(transform = this.transforms[i]).style] =
1008
- transform.unit=='color' ? '#'+
1009
- (Math.round(transform.originalValue[0]+
1010
- (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
1011
- (Math.round(transform.originalValue[1]+
1012
- (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
1013
- (Math.round(transform.originalValue[2]+
1014
- (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
1015
- transform.originalValue + Math.round(
1016
- ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
1017
- this.element.setStyle(style, true);
1018
- }
1019
- });
1020
-
1021
- Effect.Transform = Class.create();
1022
- Object.extend(Effect.Transform.prototype, {
1023
- initialize: function(tracks){
1024
- this.tracks = [];
1025
- this.options = arguments[1] || {};
1026
- this.addTracks(tracks);
1027
- },
1028
- addTracks: function(tracks){
1029
- tracks.each(function(track){
1030
- var data = $H(track).values().first();
1031
- this.tracks.push($H({
1032
- ids: $H(track).keys().first(),
1033
- effect: Effect.Morph,
1034
- options: { style: data }
1035
- }));
1036
- }.bind(this));
1037
- return this;
1038
- },
1039
- play: function(){
1040
- return new Effect.Parallel(
1041
- this.tracks.map(function(track){
1042
- var elements = [$(track.ids) || $$(track.ids)].flatten();
1043
- return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1044
- }).flatten(),
1045
- this.options
1046
- );
1047
- }
1048
- });
1049
-
1050
- Element.CSS_PROPERTIES = $w(
1051
- 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1052
- 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1053
- 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1054
- 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1055
- 'fontSize fontWeight height left letterSpacing lineHeight ' +
1056
- 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1057
- 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1058
- 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1059
- 'right textIndent top width wordSpacing zIndex');
1060
-
1061
- Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1062
-
1063
- String.prototype.parseStyle = function(){
1064
- var element = document.createElement('div');
1065
- element.innerHTML = '<div style="' + this + '"></div>';
1066
- var style = element.childNodes[0].style, styleRules = $H();
1067
-
1068
- Element.CSS_PROPERTIES.each(function(property){
1069
- if(style[property]) styleRules[property] = style[property];
1070
- });
1071
- if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
1072
- styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
1073
- }
1074
- return styleRules;
1075
- };
1076
-
1077
- Element.morph = function(element, style) {
1078
- new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1079
- return element;
1080
- };
1081
-
1082
- ['getInlineOpacity','forceRerendering','setContentZoom',
1083
- 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1084
- function(f) { Element.Methods[f] = Element[f]; }
1085
- );
1086
-
1087
- Element.Methods.visualEffect = function(element, effect, options) {
1088
- s = effect.dasherize().camelize();
1089
- effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1090
- new Effect[effect_class](element, options);
1091
- return $(element);
1092
- };
1093
-
1094
- Element.addMethods();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/dev/lib.dev.js ADDED
@@ -0,0 +1,1230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simple Lightbox
3
+ * @author Archetyped (http://archetyped.com/tools/simple-lightbox/)
4
+ *
5
+ * Inspiration/History
6
+ * > 2010: Originally based on Lightbox Slideshow v1.1
7
+ * > by Justin Barkhuff (http://www.justinbarkhuff.com/lab/lightbox_slideshow/)
8
+ * > Largely based on Lightbox v2.02
9
+ * > by Lokesh Dhakar - http://huddletogether.com/projects/lightbox2/
10
+ * > 2011-01-12: Rebuilt as jQuery-compatible codebase
11
+ *
12
+ */
13
+
14
+
15
+ (function($) {
16
+ SLB = {
17
+ /* Properties */
18
+
19
+ //Core
20
+ prefix : 'slb',
21
+ options : {},
22
+ //Activation
23
+ trigger : null,
24
+ urls_checked : {},
25
+ //Items
26
+ items : [],
27
+ item_curr : null,
28
+ item_init : null,
29
+ group : null,
30
+ media : {},
31
+ //Content
32
+ content: {
33
+ caption_enabled: true,
34
+ caption_src: true,
35
+ desc_enabled: true,
36
+ labels : {
37
+ closeLink : 'close',
38
+ loadingMsg : 'loading',
39
+ nextLink : 'next &raquo;',
40
+ prevLink : '&laquo; prev',
41
+ startSlideshow : 'start slideshow',
42
+ stopSlideshow : 'stop slideshow',
43
+ numDisplayPrefix : 'Image',
44
+ numDisplaySeparator : 'of'
45
+ }
46
+ },
47
+ //Layout
48
+ container : document,
49
+ masks : ['select','object','embed','iframe'],
50
+ layout: {
51
+ template: '',
52
+ parsed: '',
53
+ placeholders : {
54
+ slbContent: '<img id="slb_slbContent" />',
55
+ slbLoading: '<span id="slb_slbLoading">loading</span>',
56
+ slbClose: '<a class="slb_slbClose" href="#">close</a>',
57
+ navPrev: '<a class="slb_navPrev slb_nav" href="#">&laquo; prev</a>',
58
+ navNext: '<a class="slb_navNext slb_nav" href="#">&raquo; next</a>',
59
+ navSlideControl: '<a class="slb_navSlideControl" href="#">Stop</a>',
60
+ dataCaption: '<span class="slb_dataCaption"></span>',
61
+ dataDescription: '<span class="slb_dataDescription"></span>',
62
+ dataNumber: '<span class="slb_dataNumber"></span>'
63
+ }
64
+ },
65
+ //Slideshow
66
+ slideshow: {
67
+ active: null,
68
+ enabled: true,
69
+ loop: true,
70
+ duration: 4,
71
+ timer: null
72
+ },
73
+ //Animation
74
+ anim: {
75
+ active: true,
76
+ overlay_duration: 0,
77
+ overlay_opacity: .8,
78
+ resize_duration: 0
79
+ },
80
+
81
+ /* Initialization */
82
+
83
+ /**
84
+ * Initialize lightbox instance
85
+ * @param object options Instance options
86
+ */
87
+ init: function(options) {
88
+ //Options
89
+ this.options = $.extend(true, {
90
+ //Core
91
+ prefix: null,
92
+
93
+ //Activation
94
+ trigger : null,
95
+ validateLinks : false,
96
+
97
+ //Content
98
+ captionEnabled: true,
99
+ captionSrc : true,
100
+ descEnabled: true,
101
+ labels : {},
102
+
103
+ //Layout
104
+ layout : null,
105
+ placeholders : {},
106
+
107
+ //Animation
108
+ animate : true,
109
+ overlayDuration : .2,
110
+ overlayOpacity : .8,
111
+ resizeSpeed : 400,
112
+
113
+ //Slideshow
114
+ autoPlay : true,
115
+ enableSlideshow : true,
116
+ loop : true,
117
+ slideTime : 4
118
+ }, options);
119
+
120
+ //Stop if no layout is defined
121
+ if (!this.options.layout || this.options.layout.toString().length == 0)
122
+ this.end();
123
+
124
+ /* Setup Properties */
125
+
126
+ //Prefix
127
+ if ( $.type(this.options.prefix) == 'string' && this.options.prefix.length > 0 ) {
128
+ this.prefix = this.options.prefix;
129
+ }
130
+
131
+ //Activation
132
+ if ( !$.isArray(this.options.trigger) ) {
133
+ this.trigger = [this.prefix];
134
+ }
135
+
136
+ //Content
137
+ $.extend(true, this.content, {
138
+ caption_enabled: !!this.options.captionEnabled,
139
+ caption_src: !!this.options.captionSrc,
140
+ desc_enabled: !!this.options.descEnabled,
141
+ labels: ( $.isPlainObject(this.options.labels) ) ? this.options.labels : {}
142
+ });
143
+
144
+ //Layout
145
+ $.extend(true, this.layout, {
146
+ template: this.options.layout,
147
+ placeholders: this.options.placeholders
148
+ });
149
+
150
+ //Animation
151
+ $.extend(this.anim, {
152
+ overlay_opacity: Math.max(Math.min(this.options.overlayOpacity,1),0)
153
+ });
154
+ if ( this.options.animate ) {
155
+ $.extend(this.anim, {
156
+ active: true,
157
+ overlay_duration: Math.max(this.options.overlayDuration,0),
158
+ resize_duration: this.options.resizeSpeed
159
+ });
160
+ } else {
161
+ $.extend(this.anim, {
162
+ active: false,
163
+ overlay_duration: 0,
164
+ resize_duration: 0
165
+ });
166
+ }
167
+
168
+ //Slideshow
169
+ $.extend(this.slideshow, {
170
+ play: !!this.options.autoPlay,
171
+ active: !!this.options.autoPlay,
172
+ enabled: !!this.options.enableSlideshow,
173
+ loop: ( !!this.options.enableSlideshow && !!this.options.loop ),
174
+ duration: ( $.isNumeric(this.options.slideTime) ) ? parseInt(this.options.slideTime) : 0
175
+ });
176
+
177
+ /* Init Layout */
178
+
179
+ var t = this;
180
+ var body = $('body');
181
+
182
+ //Overlay
183
+ $('<div/>', {
184
+ 'id': this.getID('overlay'),
185
+ 'css': {'display': 'none'}
186
+ }).appendTo(body)
187
+ .click(function() {t.end();});
188
+
189
+ //Viewer
190
+ var viewer = $('<div/>', {
191
+ 'id': this.getID('viewer'),
192
+ 'css': {'display': 'none'}
193
+ }).appendTo(body)
194
+ .click(function() {t.end();});
195
+
196
+ //Build layout from template
197
+ this.layout.parsed = this.getLayout();
198
+
199
+ //Insert layout into viewer
200
+ $(this.layout.parsed).appendTo(viewer);
201
+
202
+ //Set UI
203
+ this.initUI();
204
+
205
+ //Add events
206
+ this.initEvents();
207
+ },
208
+
209
+ /**
210
+ * Set localized values for UI elements
211
+ */
212
+ initUI: function() {
213
+ this.get('slbClose').html(this.getLabel('closeLink'));
214
+ this.get('navNext').html(this.getLabel('nextLink'));
215
+ this.get('navPrev').html(this.getLabel('prevLink'));
216
+ this.get('navSlideControl').html( this.getLabel((this.slideshowActive()) ? 'stopSlideshow' : 'startSlideshow') );
217
+ },
218
+
219
+ /**
220
+ * Add events to various UI elements
221
+ */
222
+ initEvents: function() {
223
+ var t = this, delay = 500;
224
+ //Remove all previous handlers
225
+ this.get('container,details,navPrev,navNext,navSlideControl,slbClose').unbind('click');
226
+
227
+ //Set event handlers
228
+ this.get('container,details').click(function(ev) {
229
+ ev.stopPropagation();
230
+ });
231
+
232
+ var clickP = function() {
233
+ //Handle double clicks
234
+ t.get('navPrev').unbind('click').click(false);
235
+ setTimeout(function() {t.get('navPrev').click(clickP);}, delay);
236
+ t.navPrev();
237
+ return false;
238
+ };
239
+ this.get('navPrev').click(function(){
240
+ return clickP();
241
+ });
242
+
243
+ var clickN = function() {
244
+ //Handle double clicks
245
+ t.get('navNext').unbind('click').click(false);
246
+ setTimeout(function() {t.get('navNext').click(clickN);}, delay);
247
+ t.navNext();
248
+ return false;
249
+ };
250
+ this.get('navNext').click(function() {
251
+ return clickN();
252
+ });
253
+
254
+ this.get('navSlideControl').click(function() {
255
+ t.slideshowToggle();
256
+ return false;
257
+ });
258
+ this.get('slbClose').click(function() {
259
+ t.end();
260
+ return false;
261
+ });
262
+
263
+ //Handle links on page
264
+ this.initLinks();
265
+ },
266
+
267
+ /**
268
+ * Finds all compatible image links on page
269
+ * @return void
270
+ */
271
+ initLinks: function() {
272
+ var ph = '{relattr}', t = this;
273
+ var sel = [], selBase = 'a[href][rel*="' + ph + '"]:not([rel~="' + this.addPrefix('off') + '"])';
274
+
275
+ //Click event handler
276
+ var handler = function() {
277
+ t.view(this);
278
+ return false;
279
+ };
280
+
281
+ //Build selector
282
+ for (var x = 0; x < this.trigger.length; x++) {
283
+ sel.push(selBase.replace(ph, this.trigger[x]));
284
+ }
285
+ sel = sel.join(',');
286
+ //Add event handler to links
287
+ $(sel, $(this.container)).live('click', handler);
288
+ },
289
+
290
+ /* Display */
291
+
292
+ /**
293
+ * Display viewer
294
+ * If item is part of a group, add other items in group
295
+ * @param node item Link element of item to display
296
+ */
297
+ view: function(item) {
298
+ item = $(item);
299
+ this.mask();
300
+
301
+ this.items = [];
302
+ this.setGroup(item);
303
+
304
+ var t = this;
305
+ var groupTemp = {};
306
+ this.fileExists(this.itemSource(item),
307
+ function() {
308
+ /* File Exists */
309
+
310
+ /* Handlers */
311
+
312
+ /**
313
+ * Add item to group
314
+ * @param obj el Item to add
315
+ * @param int idx DOM position index of item
316
+ * @return int Total number of items in group
317
+ */
318
+ var addItem = function(el, idx) {
319
+ groupTemp[idx] = el;
320
+ return groupTemp.length;
321
+ };
322
+
323
+ /**
324
+ * Build final item array & launch viewer
325
+ */
326
+ var proceed = function() {
327
+ t.item_init = 0;
328
+ //Sort links by document order
329
+ var order = [], el;
330
+ for (var x in groupTemp) {
331
+ order.push(x);
332
+ }
333
+ order.sort(function(a, b) { return (a - b); });
334
+ for (x = 0; x < order.length; x++) {
335
+ el = groupTemp[order[x]];
336
+ // Check if link being evaluated is the same as the clicked link
337
+ if ($(el).get(0) == $(item).get(0)) {
338
+ t.item_init = x;
339
+ }
340
+ t.items.push({'link':t.itemSource($(el)), 'title':t.getCaption(el), 'desc': t.getDescription(el)});
341
+ }
342
+ // Calculate top offset for the viewer and display
343
+ var vwrTop = $(document).scrollTop() + ($(window).height() / 15);
344
+ t.get('viewer').css('top', vwrTop + 'px').show();
345
+ t.itemLoad(t.item_init);
346
+ };
347
+
348
+ /* Display */
349
+
350
+ //Overlay
351
+ t.get('overlay')
352
+ .height($(document).height())
353
+ .fadeTo(t.anim.overlay_duration, t.options.overlayOpacity);
354
+
355
+ //Single item (not in group)
356
+ if ( !t.hasGroup() ) {
357
+ //Display item
358
+ t.item_init = 0;
359
+ addItem(item, t.item_init);
360
+ proceed();
361
+ } else {
362
+ //Item in group
363
+ var links = $(t.container).find('a');
364
+ //Get other items in group
365
+ var grpLinks = [];
366
+ var i, link;
367
+ for ( i = 0; i < links.length; i++ ) {
368
+ link = $(links[i]);
369
+ if ( t.itemSource(link) && t.inGroup(link) ) {
370
+ //Add links in group
371
+ grpLinks.push(link);
372
+ }
373
+ }
374
+
375
+ //Loop through group links, validate, and add to items array
376
+ var processed = 0;
377
+ for ( i = 0; i < grpLinks.length; i++ ) {
378
+ link = grpLinks[i];
379
+ t.fileExists(t.itemSource($(link)),
380
+ function(args) {
381
+ /* File exists */
382
+
383
+ //Add item
384
+ var el = args.items[args.idx];
385
+ addItem(el, args.idx);
386
+ processed++;
387
+ //Display valid items after all items parsed
388
+ if ( processed == args.items.length )
389
+ proceed();
390
+ },
391
+ function(args) {
392
+ /* File does not exist */
393
+ processed++;
394
+ //Display valid items after all items parsed
395
+ if (args.idx == args.items.length)
396
+ proceed();
397
+ },
398
+ {'idx': i, 'items': grpLinks});
399
+ }
400
+ }
401
+ },
402
+ function() {
403
+ /* File does not exist */
404
+ t.end();
405
+ });
406
+ },
407
+
408
+ /**
409
+ * Close the viewer
410
+ */
411
+ end: function() {
412
+ this.keyboardDisable();
413
+ this.slideshowPause();
414
+ this.get('viewer').hide();
415
+ this.get('overlay').fadeOut(this.anim.overlay_duration);
416
+ this.unmask();
417
+ },
418
+
419
+ /**
420
+ * Resizes viewer to fit image
421
+ * @param int imgWidth Image width in pixels
422
+ * @param int imgHeight Image height in pixels
423
+ */
424
+ viewerResize: function(w, h) {
425
+ var d = this.viewerSize(w, h);
426
+ //Resize container
427
+ this.get('container').animate({width: d.width, height: d.height}, this.anim.resize_duration);
428
+ //Resize overlay
429
+ this.get('overlay').css('min-width', d.width);
430
+ this.itemShow();
431
+ },
432
+
433
+ /**
434
+ * Retrieve or build container size
435
+ * @param int w Container width to set
436
+ * @param int h Container height to set
437
+ * @return obj Container width (w)/height (h) values
438
+ */
439
+ viewerSize: function(w, h) {
440
+ var style = 'padding';
441
+ var hz = ['left', 'right'], vt = ['top', 'bottom'];
442
+ var ph = 0, pv = 0;
443
+ var t = this;
444
+ //Calculate spacing around item
445
+
446
+ var getVal = function(prop) {
447
+ var unit = 'px';
448
+ prop = style + '-' + prop;
449
+ var ptemp = t.get('content').css( prop );
450
+ if ( ptemp.indexOf(unit) == -1 ) {
451
+ ptemp = 0;
452
+ } else {
453
+ ptemp = ptemp.replace(unit, '');
454
+ }
455
+ return ( $.isNumeric(ptemp) ) ? parseFloat(ptemp) : 0;
456
+ };
457
+
458
+ //Horizontal
459
+ for ( var x = 0; x < hz.length; x++) {
460
+ ph += getVal(hz[x]);
461
+ }
462
+ //Vertical
463
+ for ( x = 0; x < vt.length; x++) {
464
+ pv += getVal(vt[x]);
465
+ }
466
+ var c = {
467
+ 'width': w + ph,
468
+ 'height': h + pv
469
+ };
470
+ return c;
471
+ },
472
+
473
+ /**
474
+ * Displays objects that may conflict with the viewer
475
+ * @param bool show (optional) Whether or not to show objects (Default: TRUE)
476
+ */
477
+ unmask: function (show) {
478
+ show = ( typeof(show) == 'undefined' ) ? true : !!show;
479
+ var vis = (show) ? 'visible' : 'hidden';
480
+ $(this.masks.join(',')).css('visibility', vis);
481
+ },
482
+
483
+ /**
484
+ * Hides objects that may conflict with the viewer
485
+ * @uses unmask() to hide objects
486
+ */
487
+ mask: function () {
488
+ this.unmask(false);
489
+ },
490
+
491
+ /* Item */
492
+
493
+ /**
494
+ * Retrieve item property
495
+ * @uses items to retrieve property from item in array
496
+ * @param int idx Item position index
497
+ * @param string prop Item property to retrieve
498
+ * @return mixed Item property (Default: empty string)
499
+ */
500
+ itemProp: function(idx, prop) {
501
+ return ( idx < this.items.length && prop in this.items[idx] ) ? this.items[idx][prop] : '';
502
+ },
503
+
504
+ /**
505
+ * Preloads requested item prior to displaying it in viewer
506
+ * @param int idx Index of item in items property
507
+ * @uses items to retrieve item at specified index
508
+ * @uses viewerResize() to resize viewer after item has loaded
509
+ */
510
+ itemLoad: function(idx) {
511
+ this.item_curr = idx;
512
+
513
+ this.keyboardDisable();
514
+ this.slideshowPause();
515
+
516
+ //Hide elements during transition
517
+ this.get('slbLoading').show();
518
+ this.get('slbContent').hide();
519
+ this.get('details').hide();
520
+ var preloader = new Image();
521
+ var t = this;
522
+
523
+ //Event handler: Display item when loaded
524
+ $(preloader).bind('load', function() {
525
+ t.get('slbContent').attr('src', preloader.src);
526
+ t.viewerResize(preloader.width, preloader.height);
527
+
528
+ //Restart slideshow if active
529
+ if ( t.slideshowActive() )
530
+ t.slideshowStart();
531
+ });
532
+
533
+ //Load image
534
+ preloader.src = this.itemProp(this.item_curr, 'link');
535
+ },
536
+
537
+ /**
538
+ * Display image and begin preloading neighbors.
539
+ */
540
+ itemShow: function() {
541
+ this.get('slbLoading').hide();
542
+ var t = this;
543
+ this.get('slbContent').fadeIn(500, function() { t.contentUpdate(); });
544
+ if ( this.hasItems() ) {
545
+ this.itemPreloadSiblings();
546
+ }
547
+ },
548
+
549
+ /**
550
+ * Preloads items surrounding current item
551
+ */
552
+ itemPreloadSiblings: function() {
553
+ //Prev
554
+ var idxPrev = ( this.itemFirst() ) ? this.items.length - 1 : this.item_curr - 1;
555
+ var itemPrev = new Image();
556
+ itemPrev.src = this.itemProp(idxPrev, 'link');
557
+
558
+ //Next
559
+ var idxNext = ( this.itemLast() ) ? 0 : this.item_curr + 1;
560
+ if ( idxNext != idxPrev ) {
561
+ var itemNext = new Image();
562
+ itemNext.src = this.itemProp(idxNext, 'link');
563
+ }
564
+ },
565
+
566
+ /**
567
+ * Check if there is at least one image to display in the viewer
568
+ * @return bool TRUE if at least one image is found
569
+ * @uses items to check for images
570
+ */
571
+ hasItem: function() {
572
+ return ( this.items.length > 0 );
573
+ },
574
+
575
+ /**
576
+ * Check if there are multiple images to display in the viewer
577
+ * @return bool TRUE if there are multiple images
578
+ * @uses items to determine the number of images
579
+ */
580
+ hasItems: function() {
581
+ return ( this.items.length > 1 );
582
+ },
583
+
584
+ /**
585
+ * Check if the current image is the first image in the list
586
+ * @return bool TRUE if image is first
587
+ * @uses item_curr to check index of current image
588
+ */
589
+ itemFirst: function() {
590
+ return ( this.item_curr == 0 );
591
+ },
592
+
593
+ /**
594
+ * Check if the current image is the last image in the list
595
+ * @return bool TRUE if image is last
596
+ * @uses item_curr to check index of current image
597
+ * @uses items to compare current image to total number of images
598
+ */
599
+ itemLast: function() {
600
+ return ( this.item_curr == this.items.length - 1 );
601
+ },
602
+
603
+ /**
604
+ * Retrieve source URI in link
605
+ * @param obj el
606
+ * @return string Source file URI
607
+ */
608
+ itemSource: function(el) {
609
+ var src = $(el).attr('href');
610
+ var attr = $(el).attr('rel') || '';
611
+ if ( attr.length ) {
612
+ //Attachment source
613
+ var mSrc = this.mediaProp(el, 'source');
614
+ //Set source using extended properties
615
+ if ( $.type(mSrc) === 'string' && mSrc.length )
616
+ src = mSrc;
617
+ }
618
+ return src;
619
+ },
620
+
621
+ /**
622
+ * Check if current link is part of a gallery
623
+ * @param obj item
624
+ * @param string gType Gallery type to check for
625
+ * @return bool Whether link is part of a gallery
626
+ */
627
+ itemGallery: function(item, gType) {
628
+ var ret = false;
629
+ var galls = {
630
+ 'wp': '.gallery-icon',
631
+ 'ng': '.ngg-gallery-thumbnail'
632
+ };
633
+
634
+
635
+ if ( typeof gType == 'undefined' || !(gType in galls) ) {
636
+ gType = 'wp';
637
+ }
638
+ return ( $(item).parent(galls[gType]).length > 0 ) ? true : false ;
639
+ },
640
+
641
+ /* Media */
642
+
643
+ /**
644
+ * Retrieve ID of media item
645
+ * @param obj el Link element
646
+ * @return string|bool Media ID (Default: false - No ID)
647
+ */
648
+ mediaId: function(el) {
649
+ var h = $(el).attr('href');
650
+ if ($.type(h) !== 'string')
651
+ h = false;
652
+ return h;
653
+ },
654
+
655
+ /**
656
+ * Retrieve Media properties
657
+ * @param obj el Link element
658
+ * @return obj Properties for Media item (Default: Empty)
659
+ */
660
+ mediaProps: function(el) {
661
+ var props = {},
662
+ mId = this.mediaId(el);
663
+ if (mId && mId in this.media) {
664
+ props = this.media[mId];
665
+ }
666
+ return props;
667
+ },
668
+
669
+ /**
670
+ * Retrieve single property for media item
671
+ * @param obj el Image link DOM element
672
+ * @param string prop Property to retrieve
673
+ * @return mixed|null Item property (Default: NULL if property does not exist)
674
+ */
675
+ mediaProp: function(el, prop) {
676
+ var props = this.mediaProps(el);
677
+ return (prop in props) ? props[prop] : null;
678
+ },
679
+
680
+ /* Content */
681
+
682
+ /**
683
+ * Retrieve specified label
684
+ * @param string id Label ID
685
+ * @param string def (optional) Default value if specified label is invalid
686
+ * @return string Label text
687
+ */
688
+ getLabel: function(id, def) {
689
+ if ( typeof def == 'undefined' ) {
690
+ def = '';
691
+ }
692
+ return ( id in this.content.labels ) ? this.content.labels[id] : def;
693
+ },
694
+
695
+ /**
696
+ * Build caption for displayed item
697
+ * @param obj item DOM link element
698
+ * @return string Image caption
699
+ */
700
+ getCaption: function(item) {
701
+ item = $(item);
702
+ var caption = '';
703
+ if ( this.content.caption_enabled ) {
704
+ var sels = {
705
+ 'capt': '.wp-caption-text',
706
+ 'gIcon': '.gallery-icon'
707
+ };
708
+ var els = {
709
+ 'link': item,
710
+ 'origin': item,
711
+ 'sibs': null,
712
+ 'img': null
713
+ };
714
+ //WP Caption
715
+ if ( this.itemGallery(els.link, 'wp') ) {
716
+ els.origin = $(els.link).parent();
717
+ }
718
+ if ( (els.sibs = $(els.origin).siblings(sels.capt)) && $(els.sibs).length > 0 ) {
719
+ caption = $.trim($(els.sibs).first().text());
720
+ }
721
+
722
+ //Fall back to image properties
723
+ if ( !caption ) {
724
+ els.img = $(els.link).find('img').first();
725
+ if ( $(els.img).length ) {
726
+ //Image title / alt
727
+ caption = $(els.img).attr('title') || $(els.img).attr('alt');
728
+ caption = $.trim(caption);
729
+ }
730
+ }
731
+
732
+ //Media properties
733
+ if ( !caption ) {
734
+ caption = this.mediaProp(els.link, 'title') || '';
735
+ caption = $.trim(caption);
736
+ }
737
+
738
+ //Fall back Link Text
739
+ if ( !caption ) {
740
+ var c = '';
741
+ if ( ( c = $.trim($(els.link).text()) ) && c.length) {
742
+ caption = c;
743
+ } else if (this.options.captionSrc) {
744
+ //Fall back to Link href
745
+ caption = $(els.link).attr('href');
746
+ var trimChars = ['/', '#', '.'];
747
+ //Trim invalid characters
748
+ while ( caption.length && $.inArray(caption.charAt(0), trimChars) != -1 )
749
+ caption = caption.substr(1);
750
+ while ( caption.length && $.inArray(caption.charAt(caption.length - 1), trimChars) != -1 )
751
+ caption = caption.substr(0, caption.length - 1);
752
+
753
+ //Strip to base file name
754
+ var idx = caption.lastIndexOf('/');
755
+ if ( -1 != idx )
756
+ caption = caption.substr(idx + 1);
757
+ //Strip extension
758
+ idx = caption.lastIndexOf('.');
759
+ if ( -1 != idx ) {
760
+ caption = caption.slice(0, idx);
761
+ }
762
+ }
763
+ caption = $.trim(caption);
764
+ }
765
+ }
766
+ return caption;
767
+ },
768
+
769
+ /**
770
+ * Retrieve item description
771
+ * @param obj item
772
+ * @return string Item description (Default: empty string)
773
+ */
774
+ getDescription: function(item) {
775
+ var desc = '';
776
+ if ( this.content.desc_enabled ) {
777
+ //Retrieve description
778
+ if ( this.itemGallery(item, 'ng') ) {
779
+ desc = $(item).attr('title');
780
+ } else {
781
+ desc = this.mediaProp(item, 'desc');
782
+ }
783
+
784
+ if (!desc)
785
+ desc = '';
786
+ }
787
+ return desc;
788
+ },
789
+
790
+ /**
791
+ * Display item details
792
+ */
793
+ contentUpdate: function() {
794
+ //Caption
795
+ if (this.content.caption_enabled) {
796
+ this.get('dataCaption').text(this.itemProp(this.item_curr, 'title'));
797
+ this.get('dataCaption').show();
798
+ } else {
799
+ this.get('dataCaption').hide();
800
+ }
801
+
802
+ //Description
803
+ this.get('dataDescription').html(this.itemProp(this.item_curr, 'desc'));
804
+
805
+ //Handle grouped items
806
+ if ( this.hasItems() ) {
807
+ var num_display = this.getLabel('numDisplayPrefix') + ' ' + (this.item_curr + 1) + ' ' + this.getLabel('numDisplaySeparator') + ' ' + this.items.length;
808
+ this.get('dataNumber')
809
+ .text(num_display)
810
+ .show();
811
+ }
812
+
813
+ //Resize content area
814
+ this.get('details').width(this.get('container').width());
815
+ this.navUpdate();
816
+ var t = this;
817
+ this.get('details').animate({height: 'show', opacity: 'show'}, 650);
818
+ },
819
+
820
+ /* Layout */
821
+
822
+ /**
823
+ * Build layout from template
824
+ * @uses options.layout
825
+ * @return string Layout markup (HTML)
826
+ */
827
+ getLayout: function() {
828
+ var l = this.layout.template;
829
+
830
+ //Expand placeholders
831
+ var ph, phs, phr;
832
+ for ( ph in this.layout.placeholders ) {
833
+ phs = '{' + ph + '}';
834
+ //Continue to next placeholder if current one is not in layout
835
+ if (l.indexOf(phs) == -1)
836
+ continue;
837
+ phr = new RegExp(phs, "g");
838
+ l = l.replace(phr, this.layout.placeholders[ph]);
839
+ }
840
+
841
+ //Return final layout
842
+ return l;
843
+
844
+ },
845
+
846
+ /* Grouping */
847
+
848
+ /**
849
+ * Sets group based on current item
850
+ * @param obj item DOM element to get group from
851
+ */
852
+ setGroup: function(item) {
853
+ this.group = this.getGroup(item);
854
+ },
855
+
856
+ /**
857
+ * Extract group name from
858
+ * @param obj item Element to extract group name from
859
+ * @return string Group name
860
+ */
861
+ getGroup: function(item) {
862
+ //Return global group property if no item specified
863
+ if ( typeof item == 'undefined' || 0 == $(item).length )
864
+ return this.group;
865
+ //Get item's group
866
+ var g = '';
867
+ var attr = $(item).attr('rel') || '';
868
+ if ( attr != '' ) {
869
+ var gTmp = '',
870
+ gSt = '[',
871
+ gEnd = ']',
872
+ search = this.addPrefix('group') + gSt,
873
+ idx,
874
+ prefix = ' ';
875
+ //Find group indicator
876
+ idx = attr.indexOf(search);
877
+ //Prefix with space to find whole word
878
+ if ( prefix != search.charAt(0) && idx > 0 ) {
879
+ search = prefix + search;
880
+ idx = attr.indexOf(search);
881
+ }
882
+ //Continue processing if value is found
883
+ if ( idx != -1 ) {
884
+ //Extract group name
885
+ gTmp = $.trim(attr.substring(idx).replace(search, ''));
886
+ //Check if group defined
887
+ if (gTmp.length > 1 && gTmp.indexOf(gEnd) > 0) {
888
+ //Extract group name
889
+ g = gTmp.substring(0, gTmp.indexOf(gEnd));
890
+ }
891
+ }
892
+ }
893
+ return g;
894
+ },
895
+
896
+ /**
897
+ * Check if item is part of current group
898
+ * @param obj item Item to check
899
+ * @return bool TRUE if item is in current group, FALSE otherwise
900
+ */
901
+ inGroup: function(item) {
902
+ return ( this.hasGroup() && ( this.getGroup(item) == this.getGroup() ) ) ? true : false;
903
+ },
904
+
905
+ /**
906
+ * Check if group is set
907
+ * @return bool TRUE if group is set, FALSE otherwise
908
+ */
909
+ hasGroup: function() {
910
+ return ( $.type(this.group) == 'string' && this.group.length > 0 ) ? true : false;
911
+ },
912
+
913
+ /* Slideshow */
914
+
915
+ /**
916
+ * Checks if slideshow is currently activated
917
+ * @return bool TRUE if slideshow is active, false otherwise
918
+ * @uses slideshow.active to check slideshow activation status
919
+ */
920
+ slideshowActive: function() {
921
+ return this.slideshow.active;
922
+ },
923
+
924
+ /**
925
+ * Start the slideshow
926
+ */
927
+ slideshowStart: function() {
928
+ this.slideshow.active = true;
929
+ var t = this;
930
+ clearInterval(this.slideshow.timer);
931
+ this.slideshow.timer = setInterval(function() { t.navNext(); t.slideshowPause(); }, this.slideshow.duration * 1000);
932
+ this.get('navSlideControl').text(this.getLabel('stopSlideshow'));
933
+ },
934
+
935
+ /**
936
+ * Stop the slideshow
937
+ */
938
+ slideshowStop: function() {
939
+ this.slideshow.active = false;
940
+ if ( this.slideshow.timer ) {
941
+ clearInterval(this.slideshow.timer);
942
+ }
943
+ this.get('navSlideControl').text(this.getLabel('startSlideshow'));
944
+ },
945
+
946
+ /**
947
+ * Toggles the slideshow status
948
+ */
949
+ slideshowToggle: function() {
950
+ if ( this.slideshowActive() ) {
951
+ this.slideshowStop();
952
+ } else {
953
+ this.slideshowStart();
954
+ }
955
+ },
956
+
957
+ /**
958
+ * Pauses the slideshow
959
+ * Stops the slideshow but does not change the slideshow's activation status
960
+ */
961
+ slideshowPause: function() {
962
+ if ( this.slideshow.timer ) {
963
+ clearInterval(this.slideshow.timer);
964
+ }
965
+ },
966
+
967
+ /* Navigation */
968
+
969
+ /**
970
+ * Display appropriate previous and next hover navigation.
971
+ */
972
+ navUpdate: function() {
973
+ if ( this.hasItems() ) {
974
+ this.get('navPrev').show();
975
+ this.get('navNext').show();
976
+ if ( this.slideshow.enabled ) {
977
+ this.get('navSlideControl').show();
978
+ if ( this.slideshowActive() ) {
979
+ this.slideshowStart();
980
+ } else {
981
+ this.slideshowStop();
982
+ }
983
+ } else {
984
+ this.get('navSlideControl').hide();
985
+ }
986
+ } else {
987
+ // Hide navigation controls when only one image exists
988
+ this.get('dataNumber').hide();
989
+ this.get('navPrev').hide();
990
+ this.get('navNext').hide();
991
+ this.get('navSlideControl').hide();
992
+ }
993
+ this.keyboardEnable();
994
+ },
995
+
996
+ /**
997
+ * Show the next image in the list
998
+ */
999
+ navNext : function() {
1000
+ if ( this.hasItems() ) {
1001
+ if ( !this.slideshow.loop && this.itemLast() ) {
1002
+ return this.end();
1003
+ }
1004
+ if ( this.itemLast() ) {
1005
+ this.navFirst();
1006
+ } else {
1007
+ this.itemLoad(this.item_curr + 1);
1008
+ }
1009
+ }
1010
+ },
1011
+
1012
+ /**
1013
+ * Show the previous image in the list
1014
+ */
1015
+ navPrev : function() {
1016
+ if ( this.hasItems() ) {
1017
+ if ( !this.slideshow.loop && this.itemFirst() ) {
1018
+ return this.end();
1019
+ }
1020
+ if ( this.itemFirst() ) {
1021
+ this.navLast();
1022
+ } else {
1023
+ this.itemLoad(this.item_curr - 1);
1024
+ }
1025
+ }
1026
+ },
1027
+
1028
+ /**
1029
+ * Show the first image in the list
1030
+ */
1031
+ navFirst : function() {
1032
+ if ( this.hasItems() ) {
1033
+ this.itemLoad(0);
1034
+ }
1035
+ },
1036
+
1037
+ /**
1038
+ * Show the last image in the list
1039
+ */
1040
+ navLast : function() {
1041
+ if ( this.hasItems() ) {
1042
+ this.itemLoad(this.items.length - 1);
1043
+ }
1044
+ },
1045
+
1046
+ /**
1047
+ * Enable image navigation via the keyboard
1048
+ */
1049
+ keyboardEnable: function() {
1050
+ var t = this;
1051
+ $(document).keydown(function(e) {
1052
+ t.keyboardControl(e);
1053
+ });
1054
+ },
1055
+
1056
+ /**
1057
+ * Disable image navigation via the keyboard
1058
+ */
1059
+ keyboardDisable: function() {
1060
+ $(document).unbind('keydown');
1061
+ },
1062
+
1063
+ /**
1064
+ * Handler for keyboard events
1065
+ * @param event e Keyboard event data
1066
+ */
1067
+ keyboardControl: function(e) {
1068
+ var code = e.which;
1069
+ var key = String.fromCharCode(code).toLowerCase();
1070
+
1071
+ if ( code == 27 || key == 'x' ) {
1072
+ //Close
1073
+ this.end();
1074
+ } else if ( code == 39 || key =='n' ) {
1075
+ //Next
1076
+ this.navNext();
1077
+ } else if ( code == 37 || key == 'p' ) {
1078
+ //Previous
1079
+ this.navPrev();
1080
+ }
1081
+ },
1082
+
1083
+ /* Helpers */
1084
+
1085
+ /**
1086
+ * Generate separator text
1087
+ * @param string sep Separator text
1088
+ * @return string Separator text
1089
+ */
1090
+ getSep: function(sep) {
1091
+ return ( typeof sep == 'undefined' ) ? '_' : sep;
1092
+ },
1093
+
1094
+ /**
1095
+ * Retrieve prefix
1096
+ * @return string Object prefix
1097
+ */
1098
+ getPrefix: function() {
1099
+ return this.prefix;
1100
+ },
1101
+
1102
+ /**
1103
+ * Add prefix to text
1104
+ * @param string txt Text to add prefix to
1105
+ * @param string sep (optional) Separator text
1106
+ * @return string Prefixed text
1107
+ */
1108
+ addPrefix: function(txt, sep) {
1109
+ return this.getPrefix() + this.getSep(sep) + txt;
1110
+ },
1111
+
1112
+ hasPrefix: function(txt) {
1113
+ return ( txt.indexOf(this.addPrefix('')) === 0 ) ? true : false;
1114
+ },
1115
+
1116
+ /**
1117
+ * Generate formatted ID for viewer-specific elements
1118
+ * @param string id Base ID of element
1119
+ * @return string Formatted ID
1120
+ */
1121
+ getID: function(id) {
1122
+ return this.addPrefix(id);
1123
+ },
1124
+
1125
+ /**
1126
+ * Generate formatted selector for viewer-specific elements
1127
+ * Compares specified ID to placeholders first, then named elements
1128
+ * Multiple selectors can be included and separated by commas (',')
1129
+ * @param string id Base ID of element
1130
+ * @uses layout.placeholders to compare id to placeholder names
1131
+ * @return string Formatted selector
1132
+ */
1133
+ getSel: function(id) {
1134
+ //Process multiple selectors
1135
+ var delim = ',', prefix = '#', sel;
1136
+ if (id.toString().indexOf(delim) != -1) {
1137
+ //Split
1138
+ var sels = id.toString().split(delim);
1139
+ //Build selector
1140
+ for (var x = 0; x < sels.length; x++) {
1141
+ sels[x] = this.getSel($.trim(sels[x]));
1142
+ }
1143
+ //Join
1144
+ sel = sels.join(delim);
1145
+ } else {
1146
+ //Single selector
1147
+ if ( id in this.layout.placeholders ) {
1148
+ var ph = $( this.layout.placeholders[id] );
1149
+ if (!ph.attr('id')) {
1150
+ //Class selector
1151
+ prefix = '.';
1152
+ }
1153
+ }
1154
+ sel = prefix + this.getID(id);
1155
+ }
1156
+
1157
+ return sel;
1158
+ },
1159
+
1160
+ /**
1161
+ * Retrieve viewer-specific element
1162
+ * @param string id Base ID of element
1163
+ * @uses getSel() to generate formatted selector for element
1164
+ * @return object jQuery object of selected element(s)
1165
+ */
1166
+ get: function(id) {
1167
+ return $(this.getSel(id));
1168
+ },
1169
+
1170
+ /**
1171
+ * Checks if file exists using AJAX request
1172
+ * @param string url File URL
1173
+ * @param callback success Callback to run if file exists
1174
+ * @param callback failure Callback to run if file does not exist
1175
+ * @param obj args Arguments for callback
1176
+ */
1177
+ fileExists: function(url, success, failure, args) {
1178
+ //Validate
1179
+ var t = this;
1180
+ if ( !$.isPlainObject(args) ) {
1181
+ args = null;
1182
+ }
1183
+ if ( !$.isFunction(failure) ) {
1184
+ failure = function() {
1185
+ t.end();
1186
+ };
1187
+ }
1188
+ if ( !$.isFunction(success) ) {
1189
+ success = failure;
1190
+ }
1191
+
1192
+ //Immediate success when validation disabled
1193
+ if ( !this.options.validateLinks ) {
1194
+ return success(args);
1195
+ }
1196
+
1197
+ //Validate link
1198
+ var statusFail = 400;
1199
+ var stateCheck = 4;
1200
+ var proceed = function(res) {
1201
+ if ( res.status < statusFail ) {
1202
+ success(args);
1203
+ } else {
1204
+ failure(args);
1205
+ }
1206
+ };
1207
+
1208
+ //Check if URL already processed
1209
+ if ( url in this.urls_checked ) {
1210
+ proceed(this.urls_checked[url]);
1211
+ } else {
1212
+ //Build AJAX request to check new file
1213
+ var req = new XMLHttpRequest();
1214
+ req.open('HEAD', url, true);
1215
+ req.onreadystatechange = function() {
1216
+ if ( stateCheck == this.readyState ) {
1217
+ t.addUrl(url, this);
1218
+ proceed(this);
1219
+ }
1220
+ };
1221
+ req.send();
1222
+ }
1223
+ },
1224
+
1225
+ addUrl: function(url, res) {
1226
+ if (!(url in this.urls_checked))
1227
+ this.urls_checked[url] = res;
1228
+ }
1229
+ };
1230
+ })(jQuery);
js/dev/lightbox.js DELETED
@@ -1,719 +0,0 @@
1
- // -----------------------------------------------------------------------------------
2
- //
3
- // Simple Lightbox
4
- // by Archetyped - http://archetyped.com/tools/simple-lightbox/
5
- // Updated: 2010-06-11
6
- //
7
- // Largely based on Lightbox Slideshow v1.1
8
- // by Justin Barkhuff - http://www.justinbarkhuff.com/lab/lightbox_slideshow/
9
- // 2007/08/15
10
- //
11
- // Largely based on Lightbox v2.02
12
- // by Lokesh Dhakar - http://huddletogether.com/projects/lightbox2/
13
- // 2006/03/31
14
- //
15
- // Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
16
- //
17
- // The code inserts html at the bottom of the page that looks similar to this:
18
- //
19
- // <div id="overlay"></div>
20
- // <div id="lightbox">
21
- // <div id="outerImageContainer">
22
- // <div id="imageContainer">
23
- // <img id="lightboxImage" />
24
- // <div id="hoverNav">
25
- // <a href="javascript:void(0);" id="prevLinkImg">&laquo; prev</a>
26
- // <a href="javascript:void(0);" id="nextLinkImg">next &raquo;</a>
27
- // </div>
28
- // <div id="loading">
29
- // <a href="javascript:void(0);" id="loadingLink">loading</a>
30
- // </div>
31
- // </div>
32
- // </div>
33
- // <div id="imageDataContainer">
34
- // <div id="imageData">
35
- // <div id="imageDetails">
36
- // <span id="caption"></span>
37
- // <span id="numberDisplay"></span>
38
- // <span id="detailsNav">
39
- // <a id="prevLinkDetails" href="javascript:void(0);">&laquo; prev</a>
40
- // <a id="nextLinkDetails" href="javascript:void(0);">next &raquo;</a>
41
- // <a id="slideShowControl" href="javascript:void(0);">stop slideshow</a>
42
- // </span>
43
- // </div>
44
- // <div id="close">
45
- // <a id="closeLink" href="javascript:void(0);">close</a>
46
- // </div>
47
- // </div>
48
- // </div>
49
- // </div>
50
- //
51
- // -----------------------------------------------------------------------------------
52
-
53
- //
54
- // Lightbox Object
55
- //
56
-
57
- var Lightbox = {
58
- activeImage : null,
59
- badObjects : ['select','object','embed'],
60
- container : null,
61
- enableSlideshow : null,
62
- groupName : null,
63
- imageArray : [],
64
- options : null,
65
- overlayDuration : null,
66
- overlayOpacity : null,
67
- playSlides : null,
68
- refTags : ['a','area'],
69
- relAttribute : null,
70
- resizeDuration : null,
71
- slideShowTimer : null,
72
- startImage : null,
73
-
74
- //
75
- // initialize()
76
- // Constructor sets class properties and configuration options and
77
- // inserts html at the bottom of the page which is used to display the shadow
78
- // overlay and the image container.
79
- //
80
- initialize: function(options) {
81
- if (!document.getElementsByTagName){ return; }
82
-
83
- this.options = $H({
84
- animate : true, // resizing animations
85
- autoPlay : true, // should slideshow start automatically
86
- borderSize : 10, // if you adjust the padding in the CSS, you will need to update this variable
87
- containerID : document, // lightbox container object
88
- enableSlideshow : true, // enable slideshow feature
89
- googleAnalytics : false, // track individual image views using Google Analytics
90
- imageDataLocation : 'south', // location of image caption information
91
- initImage : '', // ID of image link to automatically launch when upon script initialization
92
- loop : true, // whether to continuously loop slideshow images
93
- overlayDuration : .2, // time to fade in shadow overlay
94
- overlayOpacity : .8, // transparency of shadow overlay
95
- prefix : '', // ID prefix for all dynamically created html elements
96
- relAttribute : 'lightbox', // specifies the rel attribute value that triggers lightbox
97
- resizeSpeed : 7, // controls the speed of the image resizing (1=slowest and 10=fastest)
98
- showGroupName : false, // show group name of images in image details
99
- slideTime : 4, // time to display images during slideshow
100
- strings : { // allows for localization
101
- closeLink : 'close',
102
- loadingMsg : 'loading',
103
- nextLink : 'next &raquo;',
104
- prevLink : '&laquo; prev',
105
- startSlideshow : 'start slideshow',
106
- stopSlideshow : 'stop slideshow',
107
- numDisplayPrefix : 'Image',
108
- numDisplaySeparator : 'of'
109
- }
110
- }).merge(options);
111
-
112
- if(this.options.animate){
113
- this.overlayDuration = Math.max(this.options.overlayDuration,0);
114
- this.options.resizeSpeed = Math.max(Math.min(this.options.resizeSpeed,10),1);
115
- this.resizeDuration = (11 - this.options.resizeSpeed) * 0.15;
116
- }else{
117
- this.overlayDuration = 0;
118
- this.resizeDuration = 0;
119
- }
120
-
121
- this.enableSlideshow = this.options.enableSlideshow;
122
- this.overlayOpacity = Math.max(Math.min(this.options.overlayOpacity,1),0);
123
- this.playSlides = this.options.autoPlay;
124
- this.container = $(this.options.containerID);
125
- this.relAttribute = this.options.relAttribute;
126
- this.updateImageList();
127
-
128
- var objBody = this.container != document ? this.container : document.getElementsByTagName('body').item(0);
129
-
130
- var objOverlay = document.createElement('div');
131
- objOverlay.setAttribute('id',this.getID('overlay'));
132
- objOverlay.style.display = 'none';
133
- objBody.appendChild(objOverlay);
134
- Event.observe(objOverlay,'click',this.end.bindAsEventListener(this));
135
-
136
- var objLightbox = document.createElement('div');
137
- objLightbox.setAttribute('id',this.getID('lightbox'));
138
- objLightbox.style.display = 'none';
139
- objBody.appendChild(objLightbox);
140
-
141
- var objImageDataContainer = document.createElement('div');
142
- objImageDataContainer.setAttribute('id',this.getID('imageDataContainer'));
143
- objImageDataContainer.className = this.getID('clearfix');
144
-
145
- var objImageData = document.createElement('div');
146
- objImageData.setAttribute('id',this.getID('imageData'));
147
- objImageDataContainer.appendChild(objImageData);
148
-
149
- var objImageDetails = document.createElement('div');
150
- objImageDetails.setAttribute('id',this.getID('imageDetails'));
151
- objImageData.appendChild(objImageDetails);
152
-
153
- var objCaption = document.createElement('span');
154
- objCaption.setAttribute('id',this.getID('caption'));
155
- objImageDetails.appendChild(objCaption);
156
-
157
- var objNumberDisplay = document.createElement('span');
158
- objNumberDisplay.setAttribute('id',this.getID('numberDisplay'));
159
- objImageDetails.appendChild(objNumberDisplay);
160
-
161
- var objDetailsNav = document.createElement('span');
162
- objDetailsNav.setAttribute('id',this.getID('detailsNav'));
163
- objImageDetails.appendChild(objDetailsNav);
164
-
165
- var objPrevLink = document.createElement('a');
166
- objPrevLink.setAttribute('id',this.getID('prevLinkDetails'));
167
- objPrevLink.setAttribute('href','javascript:void(0);');
168
- objPrevLink.innerHTML = this.options.strings.prevLink;
169
- objDetailsNav.appendChild(objPrevLink);
170
- Event.observe(objPrevLink,'click',this.showPrev.bindAsEventListener(this));
171
-
172
- var objNextLink = document.createElement('a');
173
- objNextLink.setAttribute('id',this.getID('nextLinkDetails'));
174
- objNextLink.setAttribute('href','javascript:void(0);');
175
- objNextLink.innerHTML = this.options.strings.nextLink;
176
- objDetailsNav.appendChild(objNextLink);
177
- Event.observe(objNextLink,'click',this.showNext.bindAsEventListener(this));
178
-
179
- var objSlideShowControl = document.createElement('a');
180
- objSlideShowControl.setAttribute('id',this.getID('slideShowControl'));
181
- objSlideShowControl.setAttribute('href','javascript:void(0);');
182
- objDetailsNav.appendChild(objSlideShowControl);
183
- Event.observe(objSlideShowControl,'click',this.toggleSlideShow.bindAsEventListener(this));
184
-
185
- var objClose = document.createElement('div');
186
- objClose.setAttribute('id',this.getID('close'));
187
- objImageData.appendChild(objClose);
188
-
189
- var objCloseLink = document.createElement('a');
190
- objCloseLink.setAttribute('id',this.getID('closeLink'));
191
- objCloseLink.setAttribute('href','javascript:void(0);');
192
- objCloseLink.innerHTML = this.options.strings.closeLink;
193
- objClose.appendChild(objCloseLink);
194
- Event.observe(objCloseLink,'click',this.end.bindAsEventListener(this));
195
-
196
- if(this.options.imageDataLocation == 'north'){
197
- objLightbox.appendChild(objImageDataContainer);
198
- }
199
-
200
- var objOuterImageContainer = document.createElement('div');
201
- objOuterImageContainer.setAttribute('id',this.getID('outerImageContainer'));
202
- objLightbox.appendChild(objOuterImageContainer);
203
-
204
- var objImageContainer = document.createElement('div');
205
- objImageContainer.setAttribute('id',this.getID('imageContainer'));
206
- objOuterImageContainer.appendChild(objImageContainer);
207
-
208
- var objLightboxImage = document.createElement('img');
209
- objLightboxImage.setAttribute('id',this.getID('lightboxImage'));
210
- objImageContainer.appendChild(objLightboxImage);
211
-
212
- var objHoverNav = document.createElement('div');
213
- objHoverNav.setAttribute('id',this.getID('hoverNav'));
214
- objImageContainer.appendChild(objHoverNav);
215
-
216
- var objPrevLinkImg = document.createElement('a');
217
- objPrevLinkImg.setAttribute('id',this.getID('prevLinkImg'));
218
- objPrevLinkImg.setAttribute('href','javascript:void(0);');
219
- objHoverNav.appendChild(objPrevLinkImg);
220
- Event.observe(objPrevLinkImg,'click',this.showPrev.bindAsEventListener(this));
221
-
222
- var objNextLinkImg = document.createElement('a');
223
- objNextLinkImg.setAttribute('id',this.getID('nextLinkImg'));
224
- objNextLinkImg.setAttribute('href','javascript:void(0);');
225
- objHoverNav.appendChild(objNextLinkImg);
226
- Event.observe(objNextLinkImg,'click',this.showNext.bindAsEventListener(this));
227
-
228
- var objLoading = document.createElement('div');
229
- objLoading.setAttribute('id',this.getID('loading'));
230
- objImageContainer.appendChild(objLoading);
231
-
232
- var objLoadingLink = document.createElement('a');
233
- objLoadingLink.setAttribute('id',this.getID('loadingLink'));
234
- objLoadingLink.setAttribute('href','javascript:void(0);');
235
- objLoadingLink.innerHTML = this.options.strings.loadingMsg;
236
- objLoading.appendChild(objLoadingLink);
237
- Event.observe(objLoadingLink,'click',this.end.bindAsEventListener(this));
238
-
239
- if(this.options.imageDataLocation != 'north'){
240
- objLightbox.appendChild(objImageDataContainer);
241
- }
242
-
243
- if(this.options.initImage != ''){
244
- this.start($(this.options.initImage));
245
- }
246
- },
247
-
248
- //
249
- // updateImageList()
250
- // Loops through specific tags within 'container' looking for
251
- // 'lightbox' references and applies onclick events to them.
252
- //
253
- updateImageList: function(){
254
- var el, els, rel;
255
- for(var i=0; i < this.refTags.length; i++){
256
- els = this.container.getElementsByTagName(this.refTags[i]);
257
- for(var j=0; j < els.length; j++){
258
- el = els[j];
259
- rel = String(el.getAttribute('rel'));
260
- if (el.getAttribute('href') && (rel.toLowerCase().match(this.relAttribute))){
261
- el.onclick = function(){Lightbox.start(this); return false;}
262
- }
263
- }
264
- }
265
- },
266
-
267
- getCaption: function(imageLink) {
268
- var caption = imageLink.title || '';
269
- if ( caption == '' ) {
270
- var inner = $(imageLink).getElementsBySelector('img').first();
271
- if ( inner )
272
- caption = inner.getAttribute('title') || inner.getAttribute('alt');
273
- if ( !caption )
274
- caption = imageLink.innerHTML.stripTags() || imageLink.href || '';
275
- }
276
- return caption;
277
- },
278
-
279
- //
280
- // start()
281
- // Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
282
- //
283
- start: function(imageLink) {
284
-
285
- this.hideBadObjects();
286
-
287
- // stretch overlay to fill page and fade in
288
- var pageSize = this.getPageSize();
289
- $(this.getID('overlay')).setStyle({height:pageSize.pageHeight+'px'});
290
- new Effect.Appear(this.getID('overlay'), { duration: this.overlayDuration, from: 0, to: this.overlayOpacity });
291
-
292
- this.imageArray = [];
293
- this.groupName = null;
294
-
295
- var rel = imageLink.getAttribute('rel');
296
- var imageTitle = '';
297
-
298
- // if image is NOT part of a group..
299
- if(rel == this.relAttribute){
300
- // add single image to imageArray
301
- imageTitle = this.getCaption(imageLink);
302
- this.imageArray.push({'link':imageLink.getAttribute('href'), 'title':imageTitle});
303
- this.startImage = 0;
304
- } else {
305
- // if image is part of a group..
306
- var els = this.container.getElementsByTagName(imageLink.tagName);
307
- // loop through anchors, find other images in group, and add them to imageArray
308
- for (var i=0; i<els.length; i++){
309
- var el = els[i];
310
- if (el.getAttribute('href') && (el.getAttribute('rel') == rel)){
311
- imageTitle = this.getCaption(el);
312
- this.imageArray.push({'link':el.getAttribute('href'),'title':imageTitle});
313
- if(el == imageLink){
314
- this.startImage = this.imageArray.length-1;
315
- }
316
- }
317
- }
318
- // get group name
319
- this.groupName = rel.substring(this.relAttribute.length+1,rel.length-1);
320
- }
321
-
322
- // calculate top offset for the lightbox and display
323
- var pageScroll = this.getPageScroll();
324
- var lightboxTop = pageScroll.y + (pageSize.winHeight / 15);
325
-
326
- $(this.getID('lightbox')).setStyle({top:lightboxTop+'px'}).show();
327
- this.changeImage(this.startImage);
328
- },
329
-
330
- //
331
- // changeImage()
332
- // Hide most elements and preload image in preparation for resizing image container.
333
- //
334
- changeImage: function(imageNum){
335
- this.activeImage = imageNum;
336
-
337
- this.disableKeyboardNav();
338
- this.pauseSlideShow();
339
-
340
- // hide elements during transition
341
- $(this.getID('loading')).show();
342
- $(this.getID('lightboxImage')).hide();
343
- $(this.getID('hoverNav')).hide();
344
- $(this.getID('imageDataContainer')).hide();
345
- $(this.getID('numberDisplay')).hide();
346
- $(this.getID('detailsNav')).hide();
347
-
348
- var imgPreloader = new Image();
349
-
350
- // once image is preloaded, resize image container
351
- imgPreloader.onload=function(){
352
- $(Lightbox.getID('lightboxImage')).src = imgPreloader.src;
353
- Lightbox.resizeImageContainer(imgPreloader.width,imgPreloader.height);
354
- }
355
- imgPreloader.src = this.imageArray[this.activeImage].link;
356
-
357
- if(this.options.googleAnalytics){
358
- urchinTracker(this.imageArray[this.activeImage].link);
359
- }
360
- },
361
-
362
- //
363
- // resizeImageContainer()
364
- //
365
- resizeImageContainer: function(imgWidth,imgHeight) {
366
- // get current height and width
367
- var cDims = $(this.getID('outerImageContainer')).getDimensions();
368
-
369
- // scalars based on change from old to new
370
- var xScale = ((imgWidth + (this.options.borderSize * 2)) / cDims.width) * 100;
371
- var yScale = ((imgHeight + (this.options.borderSize * 2)) / cDims.height) * 100;
372
-
373
- // calculate size difference between new and old image, and resize if necessary
374
- var wDiff = (cDims.width - this.options.borderSize * 2) - imgWidth;
375
- var hDiff = (cDims.height - this.options.borderSize * 2) - imgHeight;
376
-
377
- if(!( hDiff == 0)){ new Effect.Scale(this.getID('outerImageContainer'), yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'}); }
378
- if(!( wDiff == 0)){ new Effect.Scale(this.getID('outerImageContainer'), xScale, {scaleY: false, delay: this.resizeDuration, duration: this.resizeDuration}); }
379
-
380
- // if new and old image are same size and no scaling transition is necessary,
381
- // do a quick pause to prevent image flicker.
382
- if((hDiff == 0) && (wDiff == 0)){
383
- if(navigator.appVersion.indexOf('MSIE')!=-1){ this.pause(250); } else { this.pause(100);}
384
- }
385
-
386
- $(this.getID('prevLinkImg')).setStyle({height:imgHeight+'px'});
387
- $(this.getID('nextLinkImg')).setStyle({height:imgHeight+'px'});
388
- $(this.getID('imageDataContainer')).setStyle({width:(imgWidth+(this.options.borderSize * 2))+'px'});
389
-
390
- this.showImage();
391
- },
392
-
393
- //
394
- // showImage()
395
- // Display image and begin preloading neighbors.
396
- //
397
- showImage: function(){
398
- $(this.getID('loading')).hide();
399
- new Effect.Appear(this.getID('lightboxImage'), { duration: 0.5, queue: 'end', afterFinish: function(){ Lightbox.updateDetails(); } });
400
- this.preloadNeighborImages();
401
- },
402
-
403
- //
404
- // updateDetails()
405
- // Display caption, image number, and bottom nav.
406
- //
407
- updateDetails: function() {
408
- $(this.getID('caption')).show();
409
- $(this.getID('caption')).update(this.imageArray[this.activeImage].title);
410
-
411
- // if image is part of set display 'Image x of y'
412
- if(this.imageArray.length > 1){
413
- var num_display = this.options.strings.numDisplayPrefix + ' ' + eval(this.activeImage + 1) + ' ' + this.options.strings.numDisplaySeparator + ' ' + this.imageArray.length;
414
- if(this.options.showGroupName && this.groupName != ''){
415
- num_display += ' '+this.options.strings.numDisplaySeparator+' '+this.groupName;
416
- }
417
- $(this.getID('numberDisplay')).update(num_display).show();
418
- if(!this.enableSlideshow){
419
- $(this.getID('slideShowControl')).hide();
420
- }
421
- $(this.getID('detailsNav')).show();
422
- }
423
-
424
- new Effect.Parallel(
425
- [ new Effect.SlideDown( this.getID('imageDataContainer'), { sync: true }),
426
- new Effect.Appear(this.getID('imageDataContainer'), { sync: true }) ],
427
- { duration:.65, afterFinish: function() { Lightbox.updateNav();} }
428
- );
429
- },
430
-
431
- //
432
- // updateNav()
433
- // Display appropriate previous and next hover navigation.
434
- //
435
- updateNav: function() {
436
- if(this.imageArray.length > 1){
437
- $(this.getID('hoverNav')).show();
438
- if(this.enableSlideshow){
439
- if(this.playSlides){
440
- this.startSlideShow();
441
- } else {
442
- this.stopSlideShow();
443
- }
444
- }
445
- }
446
- this.enableKeyboardNav();
447
- },
448
- //
449
- // startSlideShow()
450
- // Starts the slide show
451
- //
452
- startSlideShow: function(){
453
- this.playSlides = true;
454
- this.slideShowTimer = new PeriodicalExecuter(function(pe){ Lightbox.showNext(); pe.stop(); },this.options.slideTime);
455
- $(this.getID('slideShowControl')).update(this.options.strings.stopSlideshow);
456
- },
457
-
458
- //
459
- // stopSlideShow()
460
- // Stops the slide show
461
- //
462
- stopSlideShow: function(){
463
- this.playSlides = false;
464
- if(this.slideShowTimer){
465
- this.slideShowTimer.stop();
466
- }
467
- $(this.getID('slideShowControl')).update(this.options.strings.startSlideshow);
468
- },
469
-
470
- //
471
- // stopSlideShow()
472
- // Stops the slide show
473
- //
474
- toggleSlideShow: function(){
475
- if(this.playSlides){
476
- this.stopSlideShow();
477
- }else{
478
- this.startSlideShow();
479
- }
480
- },
481
-
482
- //
483
- // pauseSlideShow()
484
- // Pauses the slide show (doesn't change the value of this.playSlides)
485
- //
486
- pauseSlideShow: function(){
487
- if(this.slideShowTimer){
488
- this.slideShowTimer.stop();
489
- }
490
- },
491
-
492
- //
493
- // showNext()
494
- // Display the next image in a group
495
- //
496
- showNext : function(){
497
- if(this.imageArray.length > 1){
498
- if(!this.options.loop && ((this.activeImage == this.imageArray.length - 1 && this.startImage == 0) || (this.activeImage+1 == this.startImage))){
499
- return this.end();
500
- }
501
- if(this.activeImage == this.imageArray.length - 1){
502
- this.changeImage(0);
503
- }else{
504
- this.changeImage(this.activeImage+1);
505
- }
506
- }
507
- },
508
-
509
- //
510
- // showPrev()
511
- // Display the next image in a group
512
- //
513
- showPrev : function(){
514
- if(this.imageArray.length > 1){
515
- if(this.activeImage == 0){
516
- this.changeImage(this.imageArray.length - 1);
517
- }else{
518
- this.changeImage(this.activeImage-1);
519
- }
520
- }
521
- },
522
-
523
- //
524
- // showFirst()
525
- // Display the first image in a group
526
- //
527
- showFirst : function(){
528
- if(this.imageArray.length > 1){
529
- this.changeImage(0);
530
- }
531
- },
532
-
533
- //
534
- // showFirst()
535
- // Display the first image in a group
536
- //
537
- showLast : function(){
538
- if(this.imageArray.length > 1){
539
- this.changeImage(this.imageArray.length - 1);
540
- }
541
- },
542
-
543
- //
544
- // enableKeyboardNav()
545
- //
546
- enableKeyboardNav: function() {
547
- document.onkeydown = this.keyboardAction;
548
- },
549
-
550
- //
551
- // disableKeyboardNav()
552
- //
553
- disableKeyboardNav: function() {
554
- document.onkeydown = '';
555
- },
556
-
557
- //
558
- // keyboardAction()
559
- //
560
- keyboardAction: function(e) {
561
- if (e == null) { // ie
562
- keycode = event.keyCode;
563
- } else { // mozilla
564
- keycode = e.which;
565
- }
566
-
567
- key = String.fromCharCode(keycode).toLowerCase();
568
-
569
- if(key == 'x' || key == 'o' || key == 'c'){ // close lightbox
570
- Lightbox.end();
571
- } else if(key == 'p' || key == '%'){ // display previous image
572
- Lightbox.showPrev();
573
- } else if(key == 'n' || key =='\''){ // display next image
574
- Lightbox.showNext();
575
- } else if(key == 'f'){ // display first image
576
- Lightbox.showFirst();
577
- } else if(key == 'l'){ // display last image
578
- Lightbox.showLast();
579
- } else if(key == 's'){ // toggle slideshow
580
- if(Lightbox.imageArray.length > 0 && Lightbox.options.enableSlideshow){
581
- Lightbox.toggleSlideShow();
582
- }
583
- }
584
- },
585
-
586
- //
587
- // preloadNeighborImages()
588
- // Preload previous and next images.
589
- //
590
- preloadNeighborImages: function(){
591
- var nextImageID = this.imageArray.length - 1 == this.activeImage ? 0 : this.activeImage + 1;
592
- nextImage = new Image();
593
- nextImage.src = this.imageArray[nextImageID].link
594
-
595
- var prevImageID = this.activeImage == 0 ? this.imageArray.length - 1 : this.activeImage - 1;
596
- prevImage = new Image();
597
- prevImage.src = this.imageArray[prevImageID].link;
598
- },
599
-
600
- //
601
- // end()
602
- //
603
- end: function() {
604
- this.disableKeyboardNav();
605
- this.pauseSlideShow();
606
- $(this.getID('lightbox')).hide();
607
- new Effect.Fade(this.getID('overlay'), { duration:this.overlayDuration });
608
- this.showBadObjects();
609
- },
610
-
611
- //
612
- // showBadObjects()
613
- //
614
- showBadObjects: function (){
615
- var els;
616
- var tags = Lightbox.badObjects;
617
- for(var i=0; i<tags.length; i++){
618
- els = document.getElementsByTagName(tags[i]);
619
- for(var j=0; j<els.length; j++){
620
- $(els[j]).setStyle({visibility:'visible'});
621
- }
622
- }
623
- },
624
-
625
- //
626
- // hideBadObjects()
627
- //
628
- hideBadObjects: function (){
629
- var els;
630
- var tags = Lightbox.badObjects;
631
- for(var i=0; i<tags.length; i++){
632
- els = document.getElementsByTagName(tags[i]);
633
- for(var j=0; j<els.length; j++){
634
- $(els[j]).setStyle({visibility:'hidden'});
635
- }
636
- }
637
- },
638
-
639
- //
640
- // pause(numberMillis)
641
- // Pauses code execution for specified time. Uses busy code, not good.
642
- // Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
643
- //
644
- pause: function(numberMillis) {
645
- var now = new Date();
646
- var exitTime = now.getTime() + numberMillis;
647
- while(true){
648
- now = new Date();
649
- if (now.getTime() > exitTime)
650
- return;
651
- }
652
- },
653
-
654
- //
655
- // getPageScroll()
656
- // Returns array with x,y page scroll values.
657
- // Core code from - quirksmode.org
658
- //
659
- getPageScroll: function(){
660
- var x,y;
661
- if (self.pageYOffset) {
662
- x = self.pageXOffset;
663
- y = self.pageYOffset;
664
- } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict
665
- x = document.documentElement.scrollLeft;
666
- y = document.documentElement.scrollTop;
667
- } else if (document.body) {// all other Explorers
668
- x = document.body.scrollLeft;
669
- y = document.body.scrollTop;
670
- }
671
- return {x:x,y:y};
672
- },
673
-
674
- //
675
- // getPageSize()
676
- // Returns array with page width, height and window width, height
677
- // Core code from - quirksmode.org
678
- // Edit for Firefox by pHaez
679
- //
680
- getPageSize: function(){
681
- var scrollX,scrollY,windowX,windowY,pageX,pageY;
682
- if (window.innerHeight && window.scrollMaxY) {
683
- scrollX = document.body.scrollWidth;
684
- scrollY = window.innerHeight + window.scrollMaxY;
685
- } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
686
- scrollX = document.body.scrollWidth;
687
- scrollY = document.body.scrollHeight;
688
- } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
689
- scrollX = document.body.offsetWidth;
690
- scrollY = document.body.offsetHeight;
691
- }
692
-
693
- if (self.innerHeight) { // all except Explorer
694
- windowX = self.innerWidth;
695
- windowY = self.innerHeight;
696
- } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
697
- windowX = document.documentElement.clientWidth;
698
- windowY = document.documentElement.clientHeight;
699
- } else if (document.body) { // other Explorers
700
- windowX = document.body.clientWidth;
701
- windowY = document.body.clientHeight;
702
- }
703
-
704
- pageY = (scrollY < windowY) ? windowY : scrollY; // for small pages with total height less then height of the viewport
705
- pageX = (scrollX < windowX) ? windowX : scrollX; // for small pages with total width less then width of the viewport
706
-
707
- return {pageWidth:pageX,pageHeight:pageY,winWidth:windowX,winHeight:windowY};
708
- },
709
-
710
- //
711
- // getID()
712
- // Returns formatted Lightbox element ID
713
- //
714
- getID: function(id){
715
- return this.options.prefix+id;
716
- }
717
- }
718
-
719
- // -----------------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/dev/prototype.js DELETED
@@ -1,3271 +0,0 @@
1
- /* Prototype JavaScript framework, version 1.5.1
2
- * (c) 2005-2007 Sam Stephenson
3
- *
4
- * Prototype is freely distributable under the terms of an MIT-style license.
5
- * For details, see the Prototype web site: http://www.prototypejs.org/
6
- *
7
- /*--------------------------------------------------------------------------*/
8
-
9
- var Prototype = {
10
- Version: '1.5.1',
11
-
12
- Browser: {
13
- IE: !!(window.attachEvent && !window.opera),
14
- Opera: !!window.opera,
15
- WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
16
- Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
17
- },
18
-
19
- BrowserFeatures: {
20
- XPath: !!document.evaluate,
21
- ElementExtensions: !!window.HTMLElement,
22
- SpecificElementExtensions:
23
- (document.createElement('div').__proto__ !==
24
- document.createElement('form').__proto__)
25
- },
26
-
27
- ScriptFragment: '<script[^>]*>([\u0001-\uFFFF]*?)</script>',
28
- JSONFilter: /^\/\*-secure-\s*(.*)\s*\*\/\s*$/,
29
-
30
- emptyFunction: function() { },
31
- K: function(x) { return x }
32
- }
33
-
34
- var Class = {
35
- create: function() {
36
- return function() {
37
- this.initialize.apply(this, arguments);
38
- }
39
- }
40
- }
41
-
42
- var Abstract = new Object();
43
-
44
- Object.extend = function(destination, source) {
45
- for (var property in source) {
46
- destination[property] = source[property];
47
- }
48
- return destination;
49
- }
50
-
51
- Object.extend(Object, {
52
- inspect: function(object) {
53
- try {
54
- if (object === undefined) return 'undefined';
55
- if (object === null) return 'null';
56
- return object.inspect ? object.inspect() : object.toString();
57
- } catch (e) {
58
- if (e instanceof RangeError) return '...';
59
- throw e;
60
- }
61
- },
62
-
63
- toJSON: function(object) {
64
- var type = typeof object;
65
- switch(type) {
66
- case 'undefined':
67
- case 'function':
68
- case 'unknown': return;
69
- case 'boolean': return object.toString();
70
- }
71
- if (object === null) return 'null';
72
- if (object.toJSON) return object.toJSON();
73
- if (object.ownerDocument === document) return;
74
- var results = [];
75
- for (var property in object) {
76
- var value = Object.toJSON(object[property]);
77
- if (value !== undefined)
78
- results.push(property.toJSON() + ': ' + value);
79
- }
80
- return '{' + results.join(', ') + '}';
81
- },
82
-
83
- keys: function(object) {
84
- var keys = [];
85
- for (var property in object)
86
- keys.push(property);
87
- return keys;
88
- },
89
-
90
- values: function(object) {
91
- var values = [];
92
- for (var property in object)
93
- values.push(object[property]);
94
- return values;
95
- },
96
-
97
- clone: function(object) {
98
- return Object.extend({}, object);
99
- }
100
- });
101
-
102
- Function.prototype.bind = function() {
103
- var __method = this, args = $A(arguments), object = args.shift();
104
- return function() {
105
- return __method.apply(object, args.concat($A(arguments)));
106
- }
107
- }
108
-
109
- Function.prototype.bindAsEventListener = function(object) {
110
- var __method = this, args = $A(arguments), object = args.shift();
111
- return function(event) {
112
- return __method.apply(object, [event || window.event].concat(args));
113
- }
114
- }
115
-
116
- Object.extend(Number.prototype, {
117
- toColorPart: function() {
118
- return this.toPaddedString(2, 16);
119
- },
120
-
121
- succ: function() {
122
- return this + 1;
123
- },
124
-
125
- times: function(iterator) {
126
- $R(0, this, true).each(iterator);
127
- return this;
128
- },
129
-
130
- toPaddedString: function(length, radix) {
131
- var string = this.toString(radix || 10);
132
- return '0'.times(length - string.length) + string;
133
- },
134
-
135
- toJSON: function() {
136
- return isFinite(this) ? this.toString() : 'null';
137
- }
138
- });
139
-
140
- Date.prototype.toJSON = function() {
141
- return '"' + this.getFullYear() + '-' +
142
- (this.getMonth() + 1).toPaddedString(2) + '-' +
143
- this.getDate().toPaddedString(2) + 'T' +
144
- this.getHours().toPaddedString(2) + ':' +
145
- this.getMinutes().toPaddedString(2) + ':' +
146
- this.getSeconds().toPaddedString(2) + '"';
147
- };
148
-
149
- var Try = {
150
- these: function() {
151
- var returnValue;
152
-
153
- for (var i = 0, length = arguments.length; i < length; i++) {
154
- var lambda = arguments[i];
155
- try {
156
- returnValue = lambda();
157
- break;
158
- } catch (e) {}
159
- }
160
-
161
- return returnValue;
162
- }
163
- }
164
-
165
- /*--------------------------------------------------------------------------*/
166
-
167
- var PeriodicalExecuter = Class.create();
168
- PeriodicalExecuter.prototype = {
169
- initialize: function(callback, frequency) {
170
- this.callback = callback;
171
- this.frequency = frequency;
172
- this.currentlyExecuting = false;
173
-
174
- this.registerCallback();
175
- },
176
-
177
- registerCallback: function() {
178
- this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
179
- },
180
-
181
- stop: function() {
182
- if (!this.timer) return;
183
- clearInterval(this.timer);
184
- this.timer = null;
185
- },
186
-
187
- onTimerEvent: function() {
188
- if (!this.currentlyExecuting) {
189
- try {
190
- this.currentlyExecuting = true;
191
- this.callback(this);
192
- } finally {
193
- this.currentlyExecuting = false;
194
- }
195
- }
196
- }
197
- }
198
- Object.extend(String, {
199
- interpret: function(value) {
200
- return value == null ? '' : String(value);
201
- },
202
- specialChar: {
203
- '\b': '\\b',
204
- '\t': '\\t',
205
- '\n': '\\n',
206
- '\f': '\\f',
207
- '\r': '\\r',
208
- '\\': '\\\\'
209
- }
210
- });
211
-
212
- Object.extend(String.prototype, {
213
- gsub: function(pattern, replacement) {
214
- var result = '', source = this, match;
215
- replacement = arguments.callee.prepareReplacement(replacement);
216
-
217
- while (source.length > 0) {
218
- if (match = source.match(pattern)) {
219
- result += source.slice(0, match.index);
220
- result += String.interpret(replacement(match));
221
- source = source.slice(match.index + match[0].length);
222
- } else {
223
- result += source, source = '';
224
- }
225
- }
226
- return result;
227
- },
228
-
229
- sub: function(pattern, replacement, count) {
230
- replacement = this.gsub.prepareReplacement(replacement);
231
- count = count === undefined ? 1 : count;
232
-
233
- return this.gsub(pattern, function(match) {
234
- if (--count < 0) return match[0];
235
- return replacement(match);
236
- });
237
- },
238
-
239
- scan: function(pattern, iterator) {
240
- this.gsub(pattern, iterator);
241
- return this;
242
- },
243
-
244
- truncate: function(length, truncation) {
245
- length = length || 30;
246
- truncation = truncation === undefined ? '...' : truncation;
247
- return this.length > length ?
248
- this.slice(0, length - truncation.length) + truncation : this;
249
- },
250
-
251
- strip: function() {
252
- return this.replace(/^\s+/, '').replace(/\s+$/, '');
253
- },
254
-
255
- stripTags: function() {
256
- return this.replace(/<\/?[^>]+>/gi, '');
257
- },
258
-
259
- stripScripts: function() {
260
- return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
261
- },
262
-
263
- extractScripts: function() {
264
- var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
265
- var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
266
- return (this.match(matchAll) || []).map(function(scriptTag) {
267
- return (scriptTag.match(matchOne) || ['', ''])[1];
268
- });
269
- },
270
-
271
- evalScripts: function() {
272
- return this.extractScripts().map(function(script) { return eval(script) });
273
- },
274
-
275
- escapeHTML: function() {
276
- var self = arguments.callee;
277
- self.text.data = this;
278
- return self.div.innerHTML;
279
- },
280
-
281
- unescapeHTML: function() {
282
- var div = document.createElement('div');
283
- div.innerHTML = this.stripTags();
284
- return div.childNodes[0] ? (div.childNodes.length > 1 ?
285
- $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
286
- div.childNodes[0].nodeValue) : '';
287
- },
288
-
289
- toQueryParams: function(separator) {
290
- var match = this.strip().match(/([^?#]*)(#.*)?$/);
291
- if (!match) return {};
292
-
293
- return match[1].split(separator || '&').inject({}, function(hash, pair) {
294
- if ((pair = pair.split('='))[0]) {
295
- var key = decodeURIComponent(pair.shift());
296
- var value = pair.length > 1 ? pair.join('=') : pair[0];
297
- if (value != undefined) value = decodeURIComponent(value);
298
-
299
- if (key in hash) {
300
- if (hash[key].constructor != Array) hash[key] = [hash[key]];
301
- hash[key].push(value);
302
- }
303
- else hash[key] = value;
304
- }
305
- return hash;
306
- });
307
- },
308
-
309
- toArray: function() {
310
- return this.split('');
311
- },
312
-
313
- succ: function() {
314
- return this.slice(0, this.length - 1) +
315
- String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
316
- },
317
-
318
- times: function(count) {
319
- var result = '';
320
- for (var i = 0; i < count; i++) result += this;
321
- return result;
322
- },
323
-
324
- camelize: function() {
325
- var parts = this.split('-'), len = parts.length;
326
- if (len == 1) return parts[0];
327
-
328
- var camelized = this.charAt(0) == '-'
329
- ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
330
- : parts[0];
331
-
332
- for (var i = 1; i < len; i++)
333
- camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
334
-
335
- return camelized;
336
- },
337
-
338
- capitalize: function() {
339
- return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
340
- },
341
-
342
- underscore: function() {
343
- return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
344
- },
345
-
346
- dasherize: function() {
347
- return this.gsub(/_/,'-');
348
- },
349
-
350
- inspect: function(useDoubleQuotes) {
351
- var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
352
- var character = String.specialChar[match[0]];
353
- return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
354
- });
355
- if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
356
- return "'" + escapedString.replace(/'/g, '\\\'') + "'";
357
- },
358
-
359
- toJSON: function() {
360
- return this.inspect(true);
361
- },
362
-
363
- unfilterJSON: function(filter) {
364
- return this.sub(filter || Prototype.JSONFilter, '#{1}');
365
- },
366
-
367
- evalJSON: function(sanitize) {
368
- var json = this.unfilterJSON();
369
- try {
370
- if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json)))
371
- return eval('(' + json + ')');
372
- } catch (e) { }
373
- throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
374
- },
375
-
376
- include: function(pattern) {
377
- return this.indexOf(pattern) > -1;
378
- },
379
-
380
- startsWith: function(pattern) {
381
- return this.indexOf(pattern) === 0;
382
- },
383
-
384
- endsWith: function(pattern) {
385
- var d = this.length - pattern.length;
386
- return d >= 0 && this.lastIndexOf(pattern) === d;
387
- },
388
-
389
- empty: function() {
390
- return this == '';
391
- },
392
-
393
- blank: function() {
394
- return /^\s*$/.test(this);
395
- }
396
- });
397
-
398
- if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
399
- escapeHTML: function() {
400
- return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
401
- },
402
- unescapeHTML: function() {
403
- return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
404
- }
405
- });
406
-
407
- String.prototype.gsub.prepareReplacement = function(replacement) {
408
- if (typeof replacement == 'function') return replacement;
409
- var template = new Template(replacement);
410
- return function(match) { return template.evaluate(match) };
411
- }
412
-
413
- String.prototype.parseQuery = String.prototype.toQueryParams;
414
-
415
- Object.extend(String.prototype.escapeHTML, {
416
- div: document.createElement('div'),
417
- text: document.createTextNode('')
418
- });
419
-
420
- with (String.prototype.escapeHTML) div.appendChild(text);
421
-
422
- var Template = Class.create();
423
- Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
424
- Template.prototype = {
425
- initialize: function(template, pattern) {
426
- this.template = template.toString();
427
- this.pattern = pattern || Template.Pattern;
428
- },
429
-
430
- evaluate: function(object) {
431
- return this.template.gsub(this.pattern, function(match) {
432
- var before = match[1];
433
- if (before == '\\') return match[2];
434
- return before + String.interpret(object[match[3]]);
435
- });
436
- }
437
- }
438
-
439
- var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
440
-
441
- var Enumerable = {
442
- each: function(iterator) {
443
- var index = 0;
444
- try {
445
- this._each(function(value) {
446
- iterator(value, index++);
447
- });
448
- } catch (e) {
449
- if (e != $break) throw e;
450
- }
451
- return this;
452
- },
453
-
454
- eachSlice: function(number, iterator) {
455
- var index = -number, slices = [], array = this.toArray();
456
- while ((index += number) < array.length)
457
- slices.push(array.slice(index, index+number));
458
- return slices.map(iterator);
459
- },
460
-
461
- all: function(iterator) {
462
- var result = true;
463
- this.each(function(value, index) {
464
- result = result && !!(iterator || Prototype.K)(value, index);
465
- if (!result) throw $break;
466
- });
467
- return result;
468
- },
469
-
470
- any: function(iterator) {
471
- var result = false;
472
- this.each(function(value, index) {
473
- if (result = !!(iterator || Prototype.K)(value, index))
474
- throw $break;
475
- });
476
- return result;
477
- },
478
-
479
- collect: function(iterator) {
480
- var results = [];
481
- this.each(function(value, index) {
482
- results.push((iterator || Prototype.K)(value, index));
483
- });
484
- return results;
485
- },
486
-
487
- detect: function(iterator) {
488
- var result;
489
- this.each(function(value, index) {
490
- if (iterator(value, index)) {
491
- result = value;
492
- throw $break;
493
- }
494
- });
495
- return result;
496
- },
497
-
498
- findAll: function(iterator) {
499
- var results = [];
500
- this.each(function(value, index) {
501
- if (iterator(value, index))
502
- results.push(value);
503
- });
504
- return results;
505
- },
506
-
507
- grep: function(pattern, iterator) {
508
- var results = [];
509
- this.each(function(value, index) {
510
- var stringValue = value.toString();
511
- if (stringValue.match(pattern))
512
- results.push((iterator || Prototype.K)(value, index));
513
- })
514
- return results;
515
- },
516
-
517
- include: function(object) {
518
- var found = false;
519
- this.each(function(value) {
520
- if (value == object) {
521
- found = true;
522
- throw $break;
523
- }
524
- });
525
- return found;
526
- },
527
-
528
- inGroupsOf: function(number, fillWith) {
529
- fillWith = fillWith === undefined ? null : fillWith;
530
- return this.eachSlice(number, function(slice) {
531
- while(slice.length < number) slice.push(fillWith);
532
- return slice;
533
- });
534
- },
535
-
536
- inject: function(memo, iterator) {
537
- this.each(function(value, index) {
538
- memo = iterator(memo, value, index);
539
- });
540
- return memo;
541
- },
542
-
543
- invoke: function(method) {
544
- var args = $A(arguments).slice(1);
545
- return this.map(function(value) {
546
- return value[method].apply(value, args);
547
- });
548
- },
549
-
550
- max: function(iterator) {
551
- var result;
552
- this.each(function(value, index) {
553
- value = (iterator || Prototype.K)(value, index);
554
- if (result == undefined || value >= result)
555
- result = value;
556
- });
557
- return result;
558
- },
559
-
560
- min: function(iterator) {
561
- var result;
562
- this.each(function(value, index) {
563
- value = (iterator || Prototype.K)(value, index);
564
- if (result == undefined || value < result)
565
- result = value;
566
- });
567
- return result;
568
- },
569
-
570
- partition: function(iterator) {
571
- var trues = [], falses = [];
572
- this.each(function(value, index) {
573
- ((iterator || Prototype.K)(value, index) ?
574
- trues : falses).push(value);
575
- });
576
- return [trues, falses];
577
- },
578
-
579
- pluck: function(property) {
580
- var results = [];
581
- this.each(function(value, index) {
582
- results.push(value[property]);
583
- });
584
- return results;
585
- },
586
-
587
- reject: function(iterator) {
588
- var results = [];
589
- this.each(function(value, index) {
590
- if (!iterator(value, index))
591
- results.push(value);
592
- });
593
- return results;
594
- },
595
-
596
- sortBy: function(iterator) {
597
- return this.map(function(value, index) {
598
- return {value: value, criteria: iterator(value, index)};
599
- }).sort(function(left, right) {
600
- var a = left.criteria, b = right.criteria;
601
- return a < b ? -1 : a > b ? 1 : 0;
602
- }).pluck('value');
603
- },
604
-
605
- toArray: function() {
606
- return this.map();
607
- },
608
-
609
- zip: function() {
610
- var iterator = Prototype.K, args = $A(arguments);
611
- if (typeof args.last() == 'function')
612
- iterator = args.pop();
613
-
614
- var collections = [this].concat(args).map($A);
615
- return this.map(function(value, index) {
616
- return iterator(collections.pluck(index));
617
- });
618
- },
619
-
620
- size: function() {
621
- return this.toArray().length;
622
- },
623
-
624
- inspect: function() {
625
- return '#<Enumerable:' + this.toArray().inspect() + '>';
626
- }
627
- }
628
-
629
- Object.extend(Enumerable, {
630
- map: Enumerable.collect,
631
- find: Enumerable.detect,
632
- select: Enumerable.findAll,
633
- member: Enumerable.include,
634
- entries: Enumerable.toArray
635
- });
636
- var $A = Array.from = function(iterable) {
637
- if (!iterable) return [];
638
- if (iterable.toArray) {
639
- return iterable.toArray();
640
- } else {
641
- var results = [];
642
- for (var i = 0, length = iterable.length; i < length; i++)
643
- results.push(iterable[i]);
644
- return results;
645
- }
646
- }
647
-
648
- if (Prototype.Browser.WebKit) {
649
- $A = Array.from = function(iterable) {
650
- if (!iterable) return [];
651
- if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
652
- iterable.toArray) {
653
- return iterable.toArray();
654
- } else {
655
- var results = [];
656
- for (var i = 0, length = iterable.length; i < length; i++)
657
- results.push(iterable[i]);
658
- return results;
659
- }
660
- }
661
- }
662
-
663
- Object.extend(Array.prototype, Enumerable);
664
-
665
- if (!Array.prototype._reverse)
666
- Array.prototype._reverse = Array.prototype.reverse;
667
-
668
- Object.extend(Array.prototype, {
669
- _each: function(iterator) {
670
- for (var i = 0, length = this.length; i < length; i++)
671
- iterator(this[i]);
672
- },
673
-
674
- clear: function() {
675
- this.length = 0;
676
- return this;
677
- },
678
-
679
- first: function() {
680
- return this[0];
681
- },
682
-
683
- last: function() {
684
- return this[this.length - 1];
685
- },
686
-
687
- compact: function() {
688
- return this.select(function(value) {
689
- return value != null;
690
- });
691
- },
692
-
693
- flatten: function() {
694
- return this.inject([], function(array, value) {
695
- return array.concat(value && value.constructor == Array ?
696
- value.flatten() : [value]);
697
- });
698
- },
699
-
700
- without: function() {
701
- var values = $A(arguments);
702
- return this.select(function(value) {
703
- return !values.include(value);
704
- });
705
- },
706
-
707
- indexOf: function(object) {
708
- for (var i = 0, length = this.length; i < length; i++)
709
- if (this[i] == object) return i;
710
- return -1;
711
- },
712
-
713
- reverse: function(inline) {
714
- return (inline !== false ? this : this.toArray())._reverse();
715
- },
716
-
717
- reduce: function() {
718
- return this.length > 1 ? this : this[0];
719
- },
720
-
721
- uniq: function(sorted) {
722
- return this.inject([], function(array, value, index) {
723
- if (0 == index || (sorted ? array.last() != value : !array.include(value)))
724
- array.push(value);
725
- return array;
726
- });
727
- },
728
-
729
- clone: function() {
730
- return [].concat(this);
731
- },
732
-
733
- size: function() {
734
- return this.length;
735
- },
736
-
737
- inspect: function() {
738
- return '[' + this.map(Object.inspect).join(', ') + ']';
739
- },
740
-
741
- toJSON: function() {
742
- var results = [];
743
- this.each(function(object) {
744
- var value = Object.toJSON(object);
745
- if (value !== undefined) results.push(value);
746
- });
747
- return '[' + results.join(', ') + ']';
748
- }
749
- });
750
-
751
- Array.prototype.toArray = Array.prototype.clone;
752
-
753
- function $w(string) {
754
- string = string.strip();
755
- return string ? string.split(/\s+/) : [];
756
- }
757
-
758
- if (Prototype.Browser.Opera){
759
- Array.prototype.concat = function() {
760
- var array = [];
761
- for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
762
- for (var i = 0, length = arguments.length; i < length; i++) {
763
- if (arguments[i].constructor == Array) {
764
- for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
765
- array.push(arguments[i][j]);
766
- } else {
767
- array.push(arguments[i]);
768
- }
769
- }
770
- return array;
771
- }
772
- }
773
- var Hash = function(object) {
774
- if (object instanceof Hash) this.merge(object);
775
- else Object.extend(this, object || {});
776
- };
777
-
778
- Object.extend(Hash, {
779
- toQueryString: function(obj) {
780
- var parts = [];
781
- parts.add = arguments.callee.addPair;
782
-
783
- this.prototype._each.call(obj, function(pair) {
784
- if (!pair.key) return;
785
- var value = pair.value;
786
-
787
- if (value && typeof value == 'object') {
788
- if (value.constructor == Array) value.each(function(value) {
789
- parts.add(pair.key, value);
790
- });
791
- return;
792
- }
793
- parts.add(pair.key, value);
794
- });
795
-
796
- return parts.join('&');
797
- },
798
-
799
- toJSON: function(object) {
800
- var results = [];
801
- this.prototype._each.call(object, function(pair) {
802
- var value = Object.toJSON(pair.value);
803
- if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
804
- });
805
- return '{' + results.join(', ') + '}';
806
- }
807
- });
808
-
809
- Hash.toQueryString.addPair = function(key, value, prefix) {
810
- key = encodeURIComponent(key);
811
- if (value === undefined) this.push(key);
812
- else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
813
- }
814
-
815
- Object.extend(Hash.prototype, Enumerable);
816
- Object.extend(Hash.prototype, {
817
- _each: function(iterator) {
818
- for (var key in this) {
819
- var value = this[key];
820
- if (value && value == Hash.prototype[key]) continue;
821
-
822
- var pair = [key, value];
823
- pair.key = key;
824
- pair.value = value;
825
- iterator(pair);
826
- }
827
- },
828
-
829
- keys: function() {
830
- return this.pluck('key');
831
- },
832
-
833
- values: function() {
834
- return this.pluck('value');
835
- },
836
-
837
- merge: function(hash) {
838
- return $H(hash).inject(this, function(mergedHash, pair) {
839
- mergedHash[pair.key] = pair.value;
840
- return mergedHash;
841
- });
842
- },
843
-
844
- remove: function() {
845
- var result;
846
- for(var i = 0, length = arguments.length; i < length; i++) {
847
- var value = this[arguments[i]];
848
- if (value !== undefined){
849
- if (result === undefined) result = value;
850
- else {
851
- if (result.constructor != Array) result = [result];
852
- result.push(value)
853
- }
854
- }
855
- delete this[arguments[i]];
856
- }
857
- return result;
858
- },
859
-
860
- toQueryString: function() {
861
- return Hash.toQueryString(this);
862
- },
863
-
864
- inspect: function() {
865
- return '#<Hash:{' + this.map(function(pair) {
866
- return pair.map(Object.inspect).join(': ');
867
- }).join(', ') + '}>';
868
- },
869
-
870
- toJSON: function() {
871
- return Hash.toJSON(this);
872
- }
873
- });
874
-
875
- function $H(object) {
876
- if (object instanceof Hash) return object;
877
- return new Hash(object);
878
- };
879
-
880
- // Safari iterates over shadowed properties
881
- if (function() {
882
- var i = 0, Test = function(value) { this.key = value };
883
- Test.prototype.key = 'foo';
884
- for (var property in new Test('bar')) i++;
885
- return i > 1;
886
- }()) Hash.prototype._each = function(iterator) {
887
- var cache = [];
888
- for (var key in this) {
889
- var value = this[key];
890
- if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
891
- cache.push(key);
892
- var pair = [key, value];
893
- pair.key = key;
894
- pair.value = value;
895
- iterator(pair);
896
- }
897
- };
898
- ObjectRange = Class.create();
899
- Object.extend(ObjectRange.prototype, Enumerable);
900
- Object.extend(ObjectRange.prototype, {
901
- initialize: function(start, end, exclusive) {
902
- this.start = start;
903
- this.end = end;
904
- this.exclusive = exclusive;
905
- },
906
-
907
- _each: function(iterator) {
908
- var value = this.start;
909
- while (this.include(value)) {
910
- iterator(value);
911
- value = value.succ();
912
- }
913
- },
914
-
915
- include: function(value) {
916
- if (value < this.start)
917
- return false;
918
- if (this.exclusive)
919
- return value < this.end;
920
- return value <= this.end;
921
- }
922
- });
923
-
924
- var $R = function(start, end, exclusive) {
925
- return new ObjectRange(start, end, exclusive);
926
- }
927
-
928
- var Ajax = {
929
- getTransport: function() {
930
- return Try.these(
931
- function() {return new XMLHttpRequest()},
932
- function() {return new ActiveXObject('Msxml2.XMLHTTP')},
933
- function() {return new ActiveXObject('Microsoft.XMLHTTP')}
934
- ) || false;
935
- },
936
-
937
- activeRequestCount: 0
938
- }
939
-
940
- Ajax.Responders = {
941
- responders: [],
942
-
943
- _each: function(iterator) {
944
- this.responders._each(iterator);
945
- },
946
-
947
- register: function(responder) {
948
- if (!this.include(responder))
949
- this.responders.push(responder);
950
- },
951
-
952
- unregister: function(responder) {
953
- this.responders = this.responders.without(responder);
954
- },
955
-
956
- dispatch: function(callback, request, transport, json) {
957
- this.each(function(responder) {
958
- if (typeof responder[callback] == 'function') {
959
- try {
960
- responder[callback].apply(responder, [request, transport, json]);
961
- } catch (e) {}
962
- }
963
- });
964
- }
965
- };
966
-
967
- Object.extend(Ajax.Responders, Enumerable);
968
-
969
- Ajax.Responders.register({
970
- onCreate: function() {
971
- Ajax.activeRequestCount++;
972
- },
973
- onComplete: function() {
974
- Ajax.activeRequestCount--;
975
- }
976
- });
977
-
978
- Ajax.Base = function() {};
979
- Ajax.Base.prototype = {
980
- setOptions: function(options) {
981
- this.options = {
982
- method: 'post',
983
- asynchronous: true,
984
- contentType: 'application/x-www-form-urlencoded',
985
- encoding: 'UTF-8',
986
- parameters: ''
987
- }
988
- Object.extend(this.options, options || {});
989
-
990
- this.options.method = this.options.method.toLowerCase();
991
- if (typeof this.options.parameters == 'string')
992
- this.options.parameters = this.options.parameters.toQueryParams();
993
- }
994
- }
995
-
996
- Ajax.Request = Class.create();
997
- Ajax.Request.Events =
998
- ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
999
-
1000
- Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
1001
- _complete: false,
1002
-
1003
- initialize: function(url, options) {
1004
- this.transport = Ajax.getTransport();
1005
- this.setOptions(options);
1006
- this.request(url);
1007
- },
1008
-
1009
- request: function(url) {
1010
- this.url = url;
1011
- this.method = this.options.method;
1012
- var params = Object.clone(this.options.parameters);
1013
-
1014
- if (!['get', 'post'].include(this.method)) {
1015
- // simulate other verbs over post
1016
- params['_method'] = this.method;
1017
- this.method = 'post';
1018
- }
1019
-
1020
- this.parameters = params;
1021
-
1022
- if (params = Hash.toQueryString(params)) {
1023
- // when GET, append parameters to URL
1024
- if (this.method == 'get')
1025
- this.url += (this.url.include('?') ? '&' : '?') + params;
1026
- else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1027
- params += '&_=';
1028
- }
1029
-
1030
- try {
1031
- if (this.options.onCreate) this.options.onCreate(this.transport);
1032
- Ajax.Responders.dispatch('onCreate', this, this.transport);
1033
-
1034
- this.transport.open(this.method.toUpperCase(), this.url,
1035
- this.options.asynchronous);
1036
-
1037
- if (this.options.asynchronous)
1038
- setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
1039
-
1040
- this.transport.onreadystatechange = this.onStateChange.bind(this);
1041
- this.setRequestHeaders();
1042
-
1043
- this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1044
- this.transport.send(this.body);
1045
-
1046
- /* Force Firefox to handle ready state 4 for synchronous requests */
1047
- if (!this.options.asynchronous && this.transport.overrideMimeType)
1048
- this.onStateChange();
1049
-
1050
- }
1051
- catch (e) {
1052
- this.dispatchException(e);
1053
- }
1054
- },
1055
-
1056
- onStateChange: function() {
1057
- var readyState = this.transport.readyState;
1058
- if (readyState > 1 && !((readyState == 4) && this._complete))
1059
- this.respondToReadyState(this.transport.readyState);
1060
- },
1061
-
1062
- setRequestHeaders: function() {
1063
- var headers = {
1064
- 'X-Requested-With': 'XMLHttpRequest',
1065
- 'X-Prototype-Version': Prototype.Version,
1066
- 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1067
- };
1068
-
1069
- if (this.method == 'post') {
1070
- headers['Content-type'] = this.options.contentType +
1071
- (this.options.encoding ? '; charset=' + this.options.encoding : '');
1072
-
1073
- /* Force "Connection: close" for older Mozilla browsers to work
1074
- * around a bug where XMLHttpRequest sends an incorrect
1075
- * Content-length header. See Mozilla Bugzilla #246651.
1076
- */
1077
- if (this.transport.overrideMimeType &&
1078
- (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1079
- headers['Connection'] = 'close';
1080
- }
1081
-
1082
- // user-defined headers
1083
- if (typeof this.options.requestHeaders == 'object') {
1084
- var extras = this.options.requestHeaders;
1085
-
1086
- if (typeof extras.push == 'function')
1087
- for (var i = 0, length = extras.length; i < length; i += 2)
1088
- headers[extras[i]] = extras[i+1];
1089
- else
1090
- $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1091
- }
1092
-
1093
- for (var name in headers)
1094
- this.transport.setRequestHeader(name, headers[name]);
1095
- },
1096
-
1097
- success: function() {
1098
- return !this.transport.status
1099
- || (this.transport.status >= 200 && this.transport.status < 300);
1100
- },
1101
-
1102
- respondToReadyState: function(readyState) {
1103
- var state = Ajax.Request.Events[readyState];
1104
- var transport = this.transport, json = this.evalJSON();
1105
-
1106
- if (state == 'Complete') {
1107
- try {
1108
- this._complete = true;
1109
- (this.options['on' + this.transport.status]
1110
- || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1111
- || Prototype.emptyFunction)(transport, json);
1112
- } catch (e) {
1113
- this.dispatchException(e);
1114
- }
1115
-
1116
- var contentType = this.getHeader('Content-type');
1117
- if (contentType && contentType.strip().
1118
- match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
1119
- this.evalResponse();
1120
- }
1121
-
1122
- try {
1123
- (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
1124
- Ajax.Responders.dispatch('on' + state, this, transport, json);
1125
- } catch (e) {
1126
- this.dispatchException(e);
1127
- }
1128
-
1129
- if (state == 'Complete') {
1130
- // avoid memory leak in MSIE: clean up
1131
- this.transport.onreadystatechange = Prototype.emptyFunction;
1132
- }
1133
- },
1134
-
1135
- getHeader: function(name) {
1136
- try {
1137
- return this.transport.getResponseHeader(name);
1138
- } catch (e) { return null }
1139
- },
1140
-
1141
- evalJSON: function() {
1142
- try {
1143
- var json = this.getHeader('X-JSON');
1144
- return json ? json.evalJSON() : null;
1145
- } catch (e) { return null }
1146
- },
1147
-
1148
- evalResponse: function() {
1149
- try {
1150
- return eval((this.transport.responseText || '').unfilterJSON());
1151
- } catch (e) {
1152
- this.dispatchException(e);
1153
- }
1154
- },
1155
-
1156
- dispatchException: function(exception) {
1157
- (this.options.onException || Prototype.emptyFunction)(this, exception);
1158
- Ajax.Responders.dispatch('onException', this, exception);
1159
- }
1160
- });
1161
-
1162
- Ajax.Updater = Class.create();
1163
-
1164
- Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1165
- initialize: function(container, url, options) {
1166
- this.container = {
1167
- success: (container.success || container),
1168
- failure: (container.failure || (container.success ? null : container))
1169
- }
1170
-
1171
- this.transport = Ajax.getTransport();
1172
- this.setOptions(options);
1173
-
1174
- var onComplete = this.options.onComplete || Prototype.emptyFunction;
1175
- this.options.onComplete = (function(transport, param) {
1176
- this.updateContent();
1177
- onComplete(transport, param);
1178
- }).bind(this);
1179
-
1180
- this.request(url);
1181
- },
1182
-
1183
- updateContent: function() {
1184
- var receiver = this.container[this.success() ? 'success' : 'failure'];
1185
- var response = this.transport.responseText;
1186
-
1187
- if (!this.options.evalScripts) response = response.stripScripts();
1188
-
1189
- if (receiver = $(receiver)) {
1190
- if (this.options.insertion)
1191
- new this.options.insertion(receiver, response);
1192
- else
1193
- receiver.update(response);
1194
- }
1195
-
1196
- if (this.success()) {
1197
- if (this.onComplete)
1198
- setTimeout(this.onComplete.bind(this), 10);
1199
- }
1200
- }
1201
- });
1202
-
1203
- Ajax.PeriodicalUpdater = Class.create();
1204
- Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1205
- initialize: function(container, url, options) {
1206
- this.setOptions(options);
1207
- this.onComplete = this.options.onComplete;
1208
-
1209
- this.frequency = (this.options.frequency || 2);
1210
- this.decay = (this.options.decay || 1);
1211
-
1212
- this.updater = {};
1213
- this.container = container;
1214
- this.url = url;
1215
-
1216
- this.start();
1217
- },
1218
-
1219
- start: function() {
1220
- this.options.onComplete = this.updateComplete.bind(this);
1221
- this.onTimerEvent();
1222
- },
1223
-
1224
- stop: function() {
1225
- this.updater.options.onComplete = undefined;
1226
- clearTimeout(this.timer);
1227
- (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1228
- },
1229
-
1230
- updateComplete: function(request) {
1231
- if (this.options.decay) {
1232
- this.decay = (request.responseText == this.lastText ?
1233
- this.decay * this.options.decay : 1);
1234
-
1235
- this.lastText = request.responseText;
1236
- }
1237
- this.timer = setTimeout(this.onTimerEvent.bind(this),
1238
- this.decay * this.frequency * 1000);
1239
- },
1240
-
1241
- onTimerEvent: function() {
1242
- this.updater = new Ajax.Updater(this.container, this.url, this.options);
1243
- }
1244
- });
1245
- function $(element) {
1246
- if (arguments.length > 1) {
1247
- for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1248
- elements.push($(arguments[i]));
1249
- return elements;
1250
- }
1251
- if (typeof element == 'string')
1252
- element = document.getElementById(element);
1253
- return Element.extend(element);
1254
- }
1255
-
1256
- if (Prototype.BrowserFeatures.XPath) {
1257
- document._getElementsByXPath = function(expression, parentElement) {
1258
- var results = [];
1259
- var query = document.evaluate(expression, $(parentElement) || document,
1260
- null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1261
- for (var i = 0, length = query.snapshotLength; i < length; i++)
1262
- results.push(query.snapshotItem(i));
1263
- return results;
1264
- };
1265
-
1266
- document.getElementsByClassName = function(className, parentElement) {
1267
- var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1268
- return document._getElementsByXPath(q, parentElement);
1269
- }
1270
-
1271
- } else document.getElementsByClassName = function(className, parentElement) {
1272
- var children = ($(parentElement) || document.body).getElementsByTagName('*');
1273
- var elements = [], child;
1274
- for (var i = 0, length = children.length; i < length; i++) {
1275
- child = children[i];
1276
- if (Element.hasClassName(child, className))
1277
- elements.push(Element.extend(child));
1278
- }
1279
- return elements;
1280
- };
1281
-
1282
- /*--------------------------------------------------------------------------*/
1283
-
1284
- if (!window.Element) var Element = {};
1285
-
1286
- Element.extend = function(element) {
1287
- var F = Prototype.BrowserFeatures;
1288
- if (!element || !element.tagName || element.nodeType == 3 ||
1289
- element._extended || F.SpecificElementExtensions || element == window)
1290
- return element;
1291
-
1292
- var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
1293
- T = Element.Methods.ByTag;
1294
-
1295
- // extend methods for all tags (Safari doesn't need this)
1296
- if (!F.ElementExtensions) {
1297
- Object.extend(methods, Element.Methods),
1298
- Object.extend(methods, Element.Methods.Simulated);
1299
- }
1300
-
1301
- // extend methods for specific tags
1302
- if (T[tagName]) Object.extend(methods, T[tagName]);
1303
-
1304
- for (var property in methods) {
1305
- var value = methods[property];
1306
- if (typeof value == 'function' && !(property in element))
1307
- element[property] = cache.findOrStore(value);
1308
- }
1309
-
1310
- element._extended = Prototype.emptyFunction;
1311
- return element;
1312
- };
1313
-
1314
- Element.extend.cache = {
1315
- findOrStore: function(value) {
1316
- return this[value] = this[value] || function() {
1317
- return value.apply(null, [this].concat($A(arguments)));
1318
- }
1319
- }
1320
- };
1321
-
1322
- Element.Methods = {
1323
- visible: function(element) {
1324
- return $(element).style.display != 'none';
1325
- },
1326
-
1327
- toggle: function(element) {
1328
- element = $(element);
1329
- Element[Element.visible(element) ? 'hide' : 'show'](element);
1330
- return element;
1331
- },
1332
-
1333
- hide: function(element) {
1334
- $(element).style.display = 'none';
1335
- return element;
1336
- },
1337
-
1338
- show: function(element) {
1339
- $(element).style.display = '';
1340
- return element;
1341
- },
1342
-
1343
- remove: function(element) {
1344
- element = $(element);
1345
- element.parentNode.removeChild(element);
1346
- return element;
1347
- },
1348
-
1349
- update: function(element, html) {
1350
- html = typeof html == 'undefined' ? '' : html.toString();
1351
- $(element).innerHTML = html.stripScripts();
1352
- setTimeout(function() {html.evalScripts()}, 10);
1353
- return element;
1354
- },
1355
-
1356
- replace: function(element, html) {
1357
- element = $(element);
1358
- html = typeof html == 'undefined' ? '' : html.toString();
1359
- if (element.outerHTML) {
1360
- element.outerHTML = html.stripScripts();
1361
- } else {
1362
- var range = element.ownerDocument.createRange();
1363
- range.selectNodeContents(element);
1364
- element.parentNode.replaceChild(
1365
- range.createContextualFragment(html.stripScripts()), element);
1366
- }
1367
- setTimeout(function() {html.evalScripts()}, 10);
1368
- return element;
1369
- },
1370
-
1371
- inspect: function(element) {
1372
- element = $(element);
1373
- var result = '<' + element.tagName.toLowerCase();
1374
- $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1375
- var property = pair.first(), attribute = pair.last();
1376
- var value = (element[property] || '').toString();
1377
- if (value) result += ' ' + attribute + '=' + value.inspect(true);
1378
- });
1379
- return result + '>';
1380
- },
1381
-
1382
- recursivelyCollect: function(element, property) {
1383
- element = $(element);
1384
- var elements = [];
1385
- while (element = element[property])
1386
- if (element.nodeType == 1)
1387
- elements.push(Element.extend(element));
1388
- return elements;
1389
- },
1390
-
1391
- ancestors: function(element) {
1392
- return $(element).recursivelyCollect('parentNode');
1393
- },
1394
-
1395
- descendants: function(element) {
1396
- return $A($(element).getElementsByTagName('*')).each(Element.extend);
1397
- },
1398
-
1399
- firstDescendant: function(element) {
1400
- element = $(element).firstChild;
1401
- while (element && element.nodeType != 1) element = element.nextSibling;
1402
- return $(element);
1403
- },
1404
-
1405
- immediateDescendants: function(element) {
1406
- if (!(element = $(element).firstChild)) return [];
1407
- while (element && element.nodeType != 1) element = element.nextSibling;
1408
- if (element) return [element].concat($(element).nextSiblings());
1409
- return [];
1410
- },
1411
-
1412
- previousSiblings: function(element) {
1413
- return $(element).recursivelyCollect('previousSibling');
1414
- },
1415
-
1416
- nextSiblings: function(element) {
1417
- return $(element).recursivelyCollect('nextSibling');
1418
- },
1419
-
1420
- siblings: function(element) {
1421
- element = $(element);
1422
- return element.previousSiblings().reverse().concat(element.nextSiblings());
1423
- },
1424
-
1425
- match: function(element, selector) {
1426
- if (typeof selector == 'string')
1427
- selector = new Selector(selector);
1428
- return selector.match($(element));
1429
- },
1430
-
1431
- up: function(element, expression, index) {
1432
- element = $(element);
1433
- if (arguments.length == 1) return $(element.parentNode);
1434
- var ancestors = element.ancestors();
1435
- return expression ? Selector.findElement(ancestors, expression, index) :
1436
- ancestors[index || 0];
1437
- },
1438
-
1439
- down: function(element, expression, index) {
1440
- element = $(element);
1441
- if (arguments.length == 1) return element.firstDescendant();
1442
- var descendants = element.descendants();
1443
- return expression ? Selector.findElement(descendants, expression, index) :
1444
- descendants[index || 0];
1445
- },
1446
-
1447
- previous: function(element, expression, index) {
1448
- element = $(element);
1449
- if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1450
- var previousSiblings = element.previousSiblings();
1451
- return expression ? Selector.findElement(previousSiblings, expression, index) :
1452
- previousSiblings[index || 0];
1453
- },
1454
-
1455
- next: function(element, expression, index) {
1456
- element = $(element);
1457
- if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1458
- var nextSiblings = element.nextSiblings();
1459
- return expression ? Selector.findElement(nextSiblings, expression, index) :
1460
- nextSiblings[index || 0];
1461
- },
1462
-
1463
- getElementsBySelector: function() {
1464
- var args = $A(arguments), element = $(args.shift());
1465
- return Selector.findChildElements(element, args);
1466
- },
1467
-
1468
- getElementsByClassName: function(element, className) {
1469
- return document.getElementsByClassName(className, element);
1470
- },
1471
-
1472
- readAttribute: function(element, name) {
1473
- element = $(element);
1474
- if (Prototype.Browser.IE) {
1475
- if (!element.attributes) return null;
1476
- var t = Element._attributeTranslations;
1477
- if (t.values[name]) return t.values[name](element, name);
1478
- if (t.names[name]) name = t.names[name];
1479
- var attribute = element.attributes[name];
1480
- return attribute ? attribute.nodeValue : null;
1481
- }
1482
- return element.getAttribute(name);
1483
- },
1484
-
1485
- getHeight: function(element) {
1486
- return $(element).getDimensions().height;
1487
- },
1488
-
1489
- getWidth: function(element) {
1490
- return $(element).getDimensions().width;
1491
- },
1492
-
1493
- classNames: function(element) {
1494
- return new Element.ClassNames(element);
1495
- },
1496
-
1497
- hasClassName: function(element, className) {
1498
- if (!(element = $(element))) return;
1499
- var elementClassName = element.className;
1500
- if (elementClassName.length == 0) return false;
1501
- if (elementClassName == className ||
1502
- elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1503
- return true;
1504
- return false;
1505
- },
1506
-
1507
- addClassName: function(element, className) {
1508
- if (!(element = $(element))) return;
1509
- Element.classNames(element).add(className);
1510
- return element;
1511
- },
1512
-
1513
- removeClassName: function(element, className) {
1514
- if (!(element = $(element))) return;
1515
- Element.classNames(element).remove(className);
1516
- return element;
1517
- },
1518
-
1519
- toggleClassName: function(element, className) {
1520
- if (!(element = $(element))) return;
1521
- Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1522
- return element;
1523
- },
1524
-
1525
- observe: function() {
1526
- Event.observe.apply(Event, arguments);
1527
- return $A(arguments).first();
1528
- },
1529
-
1530
- stopObserving: function() {
1531
- Event.stopObserving.apply(Event, arguments);
1532
- return $A(arguments).first();
1533
- },
1534
-
1535
- // removes whitespace-only text node children
1536
- cleanWhitespace: function(element) {
1537
- element = $(element);
1538
- var node = element.firstChild;
1539
- while (node) {
1540
- var nextNode = node.nextSibling;
1541
- if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1542
- element.removeChild(node);
1543
- node = nextNode;
1544
- }
1545
- return element;
1546
- },
1547
-
1548
- empty: function(element) {
1549
- return $(element).innerHTML.blank();
1550
- },
1551
-
1552
- descendantOf: function(element, ancestor) {
1553
- element = $(element), ancestor = $(ancestor);
1554
- while (element = element.parentNode)
1555
- if (element == ancestor) return true;
1556
- return false;
1557
- },
1558
-
1559
- scrollTo: function(element) {
1560
- element = $(element);
1561
- var pos = Position.cumulativeOffset(element);
1562
- window.scrollTo(pos[0], pos[1]);
1563
- return element;
1564
- },
1565
-
1566
- getStyle: function(element, style) {
1567
- element = $(element);
1568
- style = style == 'float' ? 'cssFloat' : style.camelize();
1569
- var value = element.style[style];
1570
- if (!value) {
1571
- var css = document.defaultView.getComputedStyle(element, null);
1572
- value = css ? css[style] : null;
1573
- }
1574
- if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1575
- return value == 'auto' ? null : value;
1576
- },
1577
-
1578
- getOpacity: function(element) {
1579
- return $(element).getStyle('opacity');
1580
- },
1581
-
1582
- setStyle: function(element, styles, camelized) {
1583
- element = $(element);
1584
- var elementStyle = element.style;
1585
-
1586
- for (var property in styles)
1587
- if (property == 'opacity') element.setOpacity(styles[property])
1588
- else
1589
- elementStyle[(property == 'float' || property == 'cssFloat') ?
1590
- (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
1591
- (camelized ? property : property.camelize())] = styles[property];
1592
-
1593
- return element;
1594
- },
1595
-
1596
- setOpacity: function(element, value) {
1597
- element = $(element);
1598
- element.style.opacity = (value == 1 || value === '') ? '' :
1599
- (value < 0.00001) ? 0 : value;
1600
- return element;
1601
- },
1602
-
1603
- getDimensions: function(element) {
1604
- element = $(element);
1605
- var display = $(element).getStyle('display');
1606
- if (display != 'none' && display != null) // Safari bug
1607
- return {width: element.offsetWidth, height: element.offsetHeight};
1608
-
1609
- // All *Width and *Height properties give 0 on elements with display none,
1610
- // so enable the element temporarily
1611
- var els = element.style;
1612
- var originalVisibility = els.visibility;
1613
- var originalPosition = els.position;
1614
- var originalDisplay = els.display;
1615
- els.visibility = 'hidden';
1616
- els.position = 'absolute';
1617
- els.display = 'block';
1618
- var originalWidth = element.clientWidth;
1619
- var originalHeight = element.clientHeight;
1620
- els.display = originalDisplay;
1621
- els.position = originalPosition;
1622
- els.visibility = originalVisibility;
1623
- return {width: originalWidth, height: originalHeight};
1624
- },
1625
-
1626
- makePositioned: function(element) {
1627
- element = $(element);
1628
- var pos = Element.getStyle(element, 'position');
1629
- if (pos == 'static' || !pos) {
1630
- element._madePositioned = true;
1631
- element.style.position = 'relative';
1632
- // Opera returns the offset relative to the positioning context, when an
1633
- // element is position relative but top and left have not been defined
1634
- if (window.opera) {
1635
- element.style.top = 0;
1636
- element.style.left = 0;
1637
- }
1638
- }
1639
- return element;
1640
- },
1641
-
1642
- undoPositioned: function(element) {
1643
- element = $(element);
1644
- if (element._madePositioned) {
1645
- element._madePositioned = undefined;
1646
- element.style.position =
1647
- element.style.top =
1648
- element.style.left =
1649
- element.style.bottom =
1650
- element.style.right = '';
1651
- }
1652
- return element;
1653
- },
1654
-
1655
- makeClipping: function(element) {
1656
- element = $(element);
1657
- if (element._overflow) return element;
1658
- element._overflow = element.style.overflow || 'auto';
1659
- if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1660
- element.style.overflow = 'hidden';
1661
- return element;
1662
- },
1663
-
1664
- undoClipping: function(element) {
1665
- element = $(element);
1666
- if (!element._overflow) return element;
1667
- element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1668
- element._overflow = null;
1669
- return element;
1670
- }
1671
- };
1672
-
1673
- Object.extend(Element.Methods, {
1674
- childOf: Element.Methods.descendantOf,
1675
- childElements: Element.Methods.immediateDescendants
1676
- });
1677
-
1678
- if (Prototype.Browser.Opera) {
1679
- Element.Methods._getStyle = Element.Methods.getStyle;
1680
- Element.Methods.getStyle = function(element, style) {
1681
- switch(style) {
1682
- case 'left':
1683
- case 'top':
1684
- case 'right':
1685
- case 'bottom':
1686
- if (Element._getStyle(element, 'position') == 'static') return null;
1687
- default: return Element._getStyle(element, style);
1688
- }
1689
- };
1690
- }
1691
- else if (Prototype.Browser.IE) {
1692
- Element.Methods.getStyle = function(element, style) {
1693
- element = $(element);
1694
- style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
1695
- var value = element.style[style];
1696
- if (!value && element.currentStyle) value = element.currentStyle[style];
1697
-
1698
- if (style == 'opacity') {
1699
- if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1700
- if (value[1]) return parseFloat(value[1]) / 100;
1701
- return 1.0;
1702
- }
1703
-
1704
- if (value == 'auto') {
1705
- if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
1706
- return element['offset'+style.capitalize()] + 'px';
1707
- return null;
1708
- }
1709
- return value;
1710
- };
1711
-
1712
- Element.Methods.setOpacity = function(element, value) {
1713
- element = $(element);
1714
- var filter = element.getStyle('filter'), style = element.style;
1715
- if (value == 1 || value === '') {
1716
- style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
1717
- return element;
1718
- } else if (value < 0.00001) value = 0;
1719
- style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
1720
- 'alpha(opacity=' + (value * 100) + ')';
1721
- return element;
1722
- };
1723
-
1724
- // IE is missing .innerHTML support for TABLE-related elements
1725
- Element.Methods.update = function(element, html) {
1726
- element = $(element);
1727
- html = typeof html == 'undefined' ? '' : html.toString();
1728
- var tagName = element.tagName.toUpperCase();
1729
- if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1730
- var div = document.createElement('div');
1731
- switch (tagName) {
1732
- case 'THEAD':
1733
- case 'TBODY':
1734
- div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1735
- depth = 2;
1736
- break;
1737
- case 'TR':
1738
- div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1739
- depth = 3;
1740
- break;
1741
- case 'TD':
1742
- div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1743
- depth = 4;
1744
- }
1745
- $A(element.childNodes).each(function(node) { element.removeChild(node) });
1746
- depth.times(function() { div = div.firstChild });
1747
- $A(div.childNodes).each(function(node) { element.appendChild(node) });
1748
- } else {
1749
- element.innerHTML = html.stripScripts();
1750
- }
1751
- setTimeout(function() { html.evalScripts() }, 10);
1752
- return element;
1753
- }
1754
- }
1755
- else if (Prototype.Browser.Gecko) {
1756
- Element.Methods.setOpacity = function(element, value) {
1757
- element = $(element);
1758
- element.style.opacity = (value == 1) ? 0.999999 :
1759
- (value === '') ? '' : (value < 0.00001) ? 0 : value;
1760
- return element;
1761
- };
1762
- }
1763
-
1764
- Element._attributeTranslations = {
1765
- names: {
1766
- colspan: "colSpan",
1767
- rowspan: "rowSpan",
1768
- valign: "vAlign",
1769
- datetime: "dateTime",
1770
- accesskey: "accessKey",
1771
- tabindex: "tabIndex",
1772
- enctype: "encType",
1773
- maxlength: "maxLength",
1774
- readonly: "readOnly",
1775
- longdesc: "longDesc"
1776
- },
1777
- values: {
1778
- _getAttr: function(element, attribute) {
1779
- return element.getAttribute(attribute, 2);
1780
- },
1781
- _flag: function(element, attribute) {
1782
- return $(element).hasAttribute(attribute) ? attribute : null;
1783
- },
1784
- style: function(element) {
1785
- return element.style.cssText.toLowerCase();
1786
- },
1787
- title: function(element) {
1788
- var node = element.getAttributeNode('title');
1789
- return node.specified ? node.nodeValue : null;
1790
- }
1791
- }
1792
- };
1793
-
1794
- (function() {
1795
- Object.extend(this, {
1796
- href: this._getAttr,
1797
- src: this._getAttr,
1798
- type: this._getAttr,
1799
- disabled: this._flag,
1800
- checked: this._flag,
1801
- readonly: this._flag,
1802
- multiple: this._flag
1803
- });
1804
- }).call(Element._attributeTranslations.values);
1805
-
1806
- Element.Methods.Simulated = {
1807
- hasAttribute: function(element, attribute) {
1808
- var t = Element._attributeTranslations, node;
1809
- attribute = t.names[attribute] || attribute;
1810
- node = $(element).getAttributeNode(attribute);
1811
- return node && node.specified;
1812
- }
1813
- };
1814
-
1815
- Element.Methods.ByTag = {};
1816
-
1817
- Object.extend(Element, Element.Methods);
1818
-
1819
- if (!Prototype.BrowserFeatures.ElementExtensions &&
1820
- document.createElement('div').__proto__) {
1821
- window.HTMLElement = {};
1822
- window.HTMLElement.prototype = document.createElement('div').__proto__;
1823
- Prototype.BrowserFeatures.ElementExtensions = true;
1824
- }
1825
-
1826
- Element.hasAttribute = function(element, attribute) {
1827
- if (element.hasAttribute) return element.hasAttribute(attribute);
1828
- return Element.Methods.Simulated.hasAttribute(element, attribute);
1829
- };
1830
-
1831
- Element.addMethods = function(methods) {
1832
- var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
1833
-
1834
- if (!methods) {
1835
- Object.extend(Form, Form.Methods);
1836
- Object.extend(Form.Element, Form.Element.Methods);
1837
- Object.extend(Element.Methods.ByTag, {
1838
- "FORM": Object.clone(Form.Methods),
1839
- "INPUT": Object.clone(Form.Element.Methods),
1840
- "SELECT": Object.clone(Form.Element.Methods),
1841
- "TEXTAREA": Object.clone(Form.Element.Methods)
1842
- });
1843
- }
1844
-
1845
- if (arguments.length == 2) {
1846
- var tagName = methods;
1847
- methods = arguments[1];
1848
- }
1849
-
1850
- if (!tagName) Object.extend(Element.Methods, methods || {});
1851
- else {
1852
- if (tagName.constructor == Array) tagName.each(extend);
1853
- else extend(tagName);
1854
- }
1855
-
1856
- function extend(tagName) {
1857
- tagName = tagName.toUpperCase();
1858
- if (!Element.Methods.ByTag[tagName])
1859
- Element.Methods.ByTag[tagName] = {};
1860
- Object.extend(Element.Methods.ByTag[tagName], methods);
1861
- }
1862
-
1863
- function copy(methods, destination, onlyIfAbsent) {
1864
- onlyIfAbsent = onlyIfAbsent || false;
1865
- var cache = Element.extend.cache;
1866
- for (var property in methods) {
1867
- var value = methods[property];
1868
- if (!onlyIfAbsent || !(property in destination))
1869
- destination[property] = cache.findOrStore(value);
1870
- }
1871
- }
1872
-
1873
- function findDOMClass(tagName) {
1874
- var klass;
1875
- var trans = {
1876
- "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
1877
- "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
1878
- "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
1879
- "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
1880
- "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
1881
- "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
1882
- "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
1883
- "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
1884
- "FrameSet", "IFRAME": "IFrame"
1885
- };
1886
- if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
1887
- if (window[klass]) return window[klass];
1888
- klass = 'HTML' + tagName + 'Element';
1889
- if (window[klass]) return window[klass];
1890
- klass = 'HTML' + tagName.capitalize() + 'Element';
1891
- if (window[klass]) return window[klass];
1892
-
1893
- window[klass] = {};
1894
- window[klass].prototype = document.createElement(tagName).__proto__;
1895
- return window[klass];
1896
- }
1897
-
1898
- if (F.ElementExtensions) {
1899
- copy(Element.Methods, HTMLElement.prototype);
1900
- copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1901
- }
1902
-
1903
- if (F.SpecificElementExtensions) {
1904
- for (var tag in Element.Methods.ByTag) {
1905
- var klass = findDOMClass(tag);
1906
- if (typeof klass == "undefined") continue;
1907
- copy(T[tag], klass.prototype);
1908
- }
1909
- }
1910
-
1911
- Object.extend(Element, Element.Methods);
1912
- delete Element.ByTag;
1913
- };
1914
-
1915
- var Toggle = { display: Element.toggle };
1916
-
1917
- /*--------------------------------------------------------------------------*/
1918
-
1919
- Abstract.Insertion = function(adjacency) {
1920
- this.adjacency = adjacency;
1921
- }
1922
-
1923
- Abstract.Insertion.prototype = {
1924
- initialize: function(element, content) {
1925
- this.element = $(element);
1926
- this.content = content.stripScripts();
1927
-
1928
- if (this.adjacency && this.element.insertAdjacentHTML) {
1929
- try {
1930
- this.element.insertAdjacentHTML(this.adjacency, this.content);
1931
- } catch (e) {
1932
- var tagName = this.element.tagName.toUpperCase();
1933
- if (['TBODY', 'TR'].include(tagName)) {
1934
- this.insertContent(this.contentFromAnonymousTable());
1935
- } else {
1936
- throw e;
1937
- }
1938
- }
1939
- } else {
1940
- this.range = this.element.ownerDocument.createRange();
1941
- if (this.initializeRange) this.initializeRange();
1942
- this.insertContent([this.range.createContextualFragment(this.content)]);
1943
- }
1944
-
1945
- setTimeout(function() {content.evalScripts()}, 10);
1946
- },
1947
-
1948
- contentFromAnonymousTable: function() {
1949
- var div = document.createElement('div');
1950
- div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1951
- return $A(div.childNodes[0].childNodes[0].childNodes);
1952
- }
1953
- }
1954
-
1955
- var Insertion = new Object();
1956
-
1957
- Insertion.Before = Class.create();
1958
- Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1959
- initializeRange: function() {
1960
- this.range.setStartBefore(this.element);
1961
- },
1962
-
1963
- insertContent: function(fragments) {
1964
- fragments.each((function(fragment) {
1965
- this.element.parentNode.insertBefore(fragment, this.element);
1966
- }).bind(this));
1967
- }
1968
- });
1969
-
1970
- Insertion.Top = Class.create();
1971
- Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1972
- initializeRange: function() {
1973
- this.range.selectNodeContents(this.element);
1974
- this.range.collapse(true);
1975
- },
1976
-
1977
- insertContent: function(fragments) {
1978
- fragments.reverse(false).each((function(fragment) {
1979
- this.element.insertBefore(fragment, this.element.firstChild);
1980
- }).bind(this));
1981
- }
1982
- });
1983
-
1984
- Insertion.Bottom = Class.create();
1985
- Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1986
- initializeRange: function() {
1987
- this.range.selectNodeContents(this.element);
1988
- this.range.collapse(this.element);
1989
- },
1990
-
1991
- insertContent: function(fragments) {
1992
- fragments.each((function(fragment) {
1993
- this.element.appendChild(fragment);
1994
- }).bind(this));
1995
- }
1996
- });
1997
-
1998
- Insertion.After = Class.create();
1999
- Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
2000
- initializeRange: function() {
2001
- this.range.setStartAfter(this.element);
2002
- },
2003
-
2004
- insertContent: function(fragments) {
2005
- fragments.each((function(fragment) {
2006
- this.element.parentNode.insertBefore(fragment,
2007
- this.element.nextSibling);
2008
- }).bind(this));
2009
- }
2010
- });
2011
-
2012
- /*--------------------------------------------------------------------------*/
2013
-
2014
- Element.ClassNames = Class.create();
2015
- Element.ClassNames.prototype = {
2016
- initialize: function(element) {
2017
- this.element = $(element);
2018
- },
2019
-
2020
- _each: function(iterator) {
2021
- this.element.className.split(/\s+/).select(function(name) {
2022
- return name.length > 0;
2023
- })._each(iterator);
2024
- },
2025
-
2026
- set: function(className) {
2027
- this.element.className = className;
2028
- },
2029
-
2030
- add: function(classNameToAdd) {
2031
- if (this.include(classNameToAdd)) return;
2032
- this.set($A(this).concat(classNameToAdd).join(' '));
2033
- },
2034
-
2035
- remove: function(classNameToRemove) {
2036
- if (!this.include(classNameToRemove)) return;
2037
- this.set($A(this).without(classNameToRemove).join(' '));
2038
- },
2039
-
2040
- toString: function() {
2041
- return $A(this).join(' ');
2042
- }
2043
- };
2044
-
2045
- Object.extend(Element.ClassNames.prototype, Enumerable);
2046
- /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2047
- * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2048
- * license. Please see http://www.yui-ext.com/ for more information. */
2049
-
2050
- var Selector = Class.create();
2051
-
2052
- Selector.prototype = {
2053
- initialize: function(expression) {
2054
- this.expression = expression.strip();
2055
- this.compileMatcher();
2056
- },
2057
-
2058
- compileMatcher: function() {
2059
- // Selectors with namespaced attributes can't use the XPath version
2060
- if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
2061
- return this.compileXPathMatcher();
2062
-
2063
- var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2064
- c = Selector.criteria, le, p, m;
2065
-
2066
- if (Selector._cache[e]) {
2067
- this.matcher = Selector._cache[e]; return;
2068
- }
2069
- this.matcher = ["this.matcher = function(root) {",
2070
- "var r = root, h = Selector.handlers, c = false, n;"];
2071
-
2072
- while (e && le != e && (/\S/).test(e)) {
2073
- le = e;
2074
- for (var i in ps) {
2075
- p = ps[i];
2076
- if (m = e.match(p)) {
2077
- this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
2078
- new Template(c[i]).evaluate(m));
2079
- e = e.replace(m[0], '');
2080
- break;
2081
- }
2082
- }
2083
- }
2084
-
2085
- this.matcher.push("return h.unique(n);\n}");
2086
- eval(this.matcher.join('\n'));
2087
- Selector._cache[this.expression] = this.matcher;
2088
- },
2089
-
2090
- compileXPathMatcher: function() {
2091
- var e = this.expression, ps = Selector.patterns,
2092
- x = Selector.xpath, le, m;
2093
-
2094
- if (Selector._cache[e]) {
2095
- this.xpath = Selector._cache[e]; return;
2096
- }
2097
-
2098
- this.matcher = ['.//*'];
2099
- while (e && le != e && (/\S/).test(e)) {
2100
- le = e;
2101
- for (var i in ps) {
2102
- if (m = e.match(ps[i])) {
2103
- this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
2104
- new Template(x[i]).evaluate(m));
2105
- e = e.replace(m[0], '');
2106
- break;
2107
- }
2108
- }
2109
- }
2110
-
2111
- this.xpath = this.matcher.join('');
2112
- Selector._cache[this.expression] = this.xpath;
2113
- },
2114
-
2115
- findElements: function(root) {
2116
- root = root || document;
2117
- if (this.xpath) return document._getElementsByXPath(this.xpath, root);
2118
- return this.matcher(root);
2119
- },
2120
-
2121
- match: function(element) {
2122
- return this.findElements(document).include(element);
2123
- },
2124
-
2125
- toString: function() {
2126
- return this.expression;
2127
- },
2128
-
2129
- inspect: function() {
2130
- return "#<Selector:" + this.expression.inspect() + ">";
2131
- }
2132
- };
2133
-
2134
- Object.extend(Selector, {
2135
- _cache: {},
2136
-
2137
- xpath: {
2138
- descendant: "//*",
2139
- child: "/*",
2140
- adjacent: "/following-sibling::*[1]",
2141
- laterSibling: '/following-sibling::*',
2142
- tagName: function(m) {
2143
- if (m[1] == '*') return '';
2144
- return "[local-name()='" + m[1].toLowerCase() +
2145
- "' or local-name()='" + m[1].toUpperCase() + "']";
2146
- },
2147
- className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2148
- id: "[@id='#{1}']",
2149
- attrPresence: "[@#{1}]",
2150
- attr: function(m) {
2151
- m[3] = m[5] || m[6];
2152
- return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2153
- },
2154
- pseudo: function(m) {
2155
- var h = Selector.xpath.pseudos[m[1]];
2156
- if (!h) return '';
2157
- if (typeof h === 'function') return h(m);
2158
- return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2159
- },
2160
- operators: {
2161
- '=': "[@#{1}='#{3}']",
2162
- '!=': "[@#{1}!='#{3}']",
2163
- '^=': "[starts-with(@#{1}, '#{3}')]",
2164
- '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2165
- '*=': "[contains(@#{1}, '#{3}')]",
2166
- '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2167
- '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2168
- },
2169
- pseudos: {
2170
- 'first-child': '[not(preceding-sibling::*)]',
2171
- 'last-child': '[not(following-sibling::*)]',
2172
- 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2173
- 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2174
- 'checked': "[@checked]",
2175
- 'disabled': "[@disabled]",
2176
- 'enabled': "[not(@disabled)]",
2177
- 'not': function(m) {
2178
- var e = m[6], p = Selector.patterns,
2179
- x = Selector.xpath, le, m, v;
2180
-
2181
- var exclusion = [];
2182
- while (e && le != e && (/\S/).test(e)) {
2183
- le = e;
2184
- for (var i in p) {
2185
- if (m = e.match(p[i])) {
2186
- v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
2187
- exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2188
- e = e.replace(m[0], '');
2189
- break;
2190
- }
2191
- }
2192
- }
2193
- return "[not(" + exclusion.join(" and ") + ")]";
2194
- },
2195
- 'nth-child': function(m) {
2196
- return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2197
- },
2198
- 'nth-last-child': function(m) {
2199
- return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2200
- },
2201
- 'nth-of-type': function(m) {
2202
- return Selector.xpath.pseudos.nth("position() ", m);
2203
- },
2204
- 'nth-last-of-type': function(m) {
2205
- return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2206
- },
2207
- 'first-of-type': function(m) {
2208
- m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2209
- },
2210
- 'last-of-type': function(m) {
2211
- m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2212
- },
2213
- 'only-of-type': function(m) {
2214
- var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2215
- },
2216
- nth: function(fragment, m) {
2217
- var mm, formula = m[6], predicate;
2218
- if (formula == 'even') formula = '2n+0';
2219
- if (formula == 'odd') formula = '2n+1';
2220
- if (mm = formula.match(/^(\d+)$/)) // digit only
2221
- return '[' + fragment + "= " + mm[1] + ']';
2222
- if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2223
- if (mm[1] == "-") mm[1] = -1;
2224
- var a = mm[1] ? Number(mm[1]) : 1;
2225
- var b = mm[2] ? Number(mm[2]) : 0;
2226
- predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2227
- "((#{fragment} - #{b}) div #{a} >= 0)]";
2228
- return new Template(predicate).evaluate({
2229
- fragment: fragment, a: a, b: b });
2230
- }
2231
- }
2232
- }
2233
- },
2234
-
2235
- criteria: {
2236
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
2237
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
2238
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
2239
- attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2240
- attr: function(m) {
2241
- m[3] = (m[5] || m[6]);
2242
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
2243
- },
2244
- pseudo: function(m) {
2245
- if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
2246
- return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
2247
- },
2248
- descendant: 'c = "descendant";',
2249
- child: 'c = "child";',
2250
- adjacent: 'c = "adjacent";',
2251
- laterSibling: 'c = "laterSibling";'
2252
- },
2253
-
2254
- patterns: {
2255
- // combinators must be listed first
2256
- // (and descendant needs to be last combinator)
2257
- laterSibling: /^\s*~\s*/,
2258
- child: /^\s*>\s*/,
2259
- adjacent: /^\s*\+\s*/,
2260
- descendant: /^\s/,
2261
-
2262
- // selectors follow
2263
- tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
2264
- id: /^#([\w\-\*]+)(\b|$)/,
2265
- className: /^\.([\w\-\*]+)(\b|$)/,
2266
- pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
2267
- attrPresence: /^\[([\w]+)\]/,
2268
- attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
2269
- },
2270
-
2271
- handlers: {
2272
- // UTILITY FUNCTIONS
2273
- // joins two collections
2274
- concat: function(a, b) {
2275
- for (var i = 0, node; node = b[i]; i++)
2276
- a.push(node);
2277
- return a;
2278
- },
2279
-
2280
- // marks an array of nodes for counting
2281
- mark: function(nodes) {
2282
- for (var i = 0, node; node = nodes[i]; i++)
2283
- node._counted = true;
2284
- return nodes;
2285
- },
2286
-
2287
- unmark: function(nodes) {
2288
- for (var i = 0, node; node = nodes[i]; i++)
2289
- node._counted = undefined;
2290
- return nodes;
2291
- },
2292
-
2293
- // mark each child node with its position (for nth calls)
2294
- // "ofType" flag indicates whether we're indexing for nth-of-type
2295
- // rather than nth-child
2296
- index: function(parentNode, reverse, ofType) {
2297
- parentNode._counted = true;
2298
- if (reverse) {
2299
- for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
2300
- node = nodes[i];
2301
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2302
- }
2303
- } else {
2304
- for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
2305
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2306
- }
2307
- },
2308
-
2309
- // filters out duplicates and extends all nodes
2310
- unique: function(nodes) {
2311
- if (nodes.length == 0) return nodes;
2312
- var results = [], n;
2313
- for (var i = 0, l = nodes.length; i < l; i++)
2314
- if (!(n = nodes[i])._counted) {
2315
- n._counted = true;
2316
- results.push(Element.extend(n));
2317
- }
2318
- return Selector.handlers.unmark(results);
2319
- },
2320
-
2321
- // COMBINATOR FUNCTIONS
2322
- descendant: function(nodes) {
2323
- var h = Selector.handlers;
2324
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2325
- h.concat(results, node.getElementsByTagName('*'));
2326
- return results;
2327
- },
2328
-
2329
- child: function(nodes) {
2330
- var h = Selector.handlers;
2331
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
2332
- for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
2333
- if (child.nodeType == 1 && child.tagName != '!') results.push(child);
2334
- }
2335
- return results;
2336
- },
2337
-
2338
- adjacent: function(nodes) {
2339
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
2340
- var next = this.nextElementSibling(node);
2341
- if (next) results.push(next);
2342
- }
2343
- return results;
2344
- },
2345
-
2346
- laterSibling: function(nodes) {
2347
- var h = Selector.handlers;
2348
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2349
- h.concat(results, Element.nextSiblings(node));
2350
- return results;
2351
- },
2352
-
2353
- nextElementSibling: function(node) {
2354
- while (node = node.nextSibling)
2355
- if (node.nodeType == 1) return node;
2356
- return null;
2357
- },
2358
-
2359
- previousElementSibling: function(node) {
2360
- while (node = node.previousSibling)
2361
- if (node.nodeType == 1) return node;
2362
- return null;
2363
- },
2364
-
2365
- // TOKEN FUNCTIONS
2366
- tagName: function(nodes, root, tagName, combinator) {
2367
- tagName = tagName.toUpperCase();
2368
- var results = [], h = Selector.handlers;
2369
- if (nodes) {
2370
- if (combinator) {
2371
- // fastlane for ordinary descendant combinators
2372
- if (combinator == "descendant") {
2373
- for (var i = 0, node; node = nodes[i]; i++)
2374
- h.concat(results, node.getElementsByTagName(tagName));
2375
- return results;
2376
- } else nodes = this[combinator](nodes);
2377
- if (tagName == "*") return nodes;
2378
- }
2379
- for (var i = 0, node; node = nodes[i]; i++)
2380
- if (node.tagName.toUpperCase() == tagName) results.push(node);
2381
- return results;
2382
- } else return root.getElementsByTagName(tagName);
2383
- },
2384
-
2385
- id: function(nodes, root, id, combinator) {
2386
- var targetNode = $(id), h = Selector.handlers;
2387
- if (!nodes && root == document) return targetNode ? [targetNode] : [];
2388
- if (nodes) {
2389
- if (combinator) {
2390
- if (combinator == 'child') {
2391
- for (var i = 0, node; node = nodes[i]; i++)
2392
- if (targetNode.parentNode == node) return [targetNode];
2393
- } else if (combinator == 'descendant') {
2394
- for (var i = 0, node; node = nodes[i]; i++)
2395
- if (Element.descendantOf(targetNode, node)) return [targetNode];
2396
- } else if (combinator == 'adjacent') {
2397
- for (var i = 0, node; node = nodes[i]; i++)
2398
- if (Selector.handlers.previousElementSibling(targetNode) == node)
2399
- return [targetNode];
2400
- } else nodes = h[combinator](nodes);
2401
- }
2402
- for (var i = 0, node; node = nodes[i]; i++)
2403
- if (node == targetNode) return [targetNode];
2404
- return [];
2405
- }
2406
- return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
2407
- },
2408
-
2409
- className: function(nodes, root, className, combinator) {
2410
- if (nodes && combinator) nodes = this[combinator](nodes);
2411
- return Selector.handlers.byClassName(nodes, root, className);
2412
- },
2413
-
2414
- byClassName: function(nodes, root, className) {
2415
- if (!nodes) nodes = Selector.handlers.descendant([root]);
2416
- var needle = ' ' + className + ' ';
2417
- for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
2418
- nodeClassName = node.className;
2419
- if (nodeClassName.length == 0) continue;
2420
- if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
2421
- results.push(node);
2422
- }
2423
- return results;
2424
- },
2425
-
2426
- attrPresence: function(nodes, root, attr) {
2427
- var results = [];
2428
- for (var i = 0, node; node = nodes[i]; i++)
2429
- if (Element.hasAttribute(node, attr)) results.push(node);
2430
- return results;
2431
- },
2432
-
2433
- attr: function(nodes, root, attr, value, operator) {
2434
- if (!nodes) nodes = root.getElementsByTagName("*");
2435
- var handler = Selector.operators[operator], results = [];
2436
- for (var i = 0, node; node = nodes[i]; i++) {
2437
- var nodeValue = Element.readAttribute(node, attr);
2438
- if (nodeValue === null) continue;
2439
- if (handler(nodeValue, value)) results.push(node);
2440
- }
2441
- return results;
2442
- },
2443
-
2444
- pseudo: function(nodes, name, value, root, combinator) {
2445
- if (nodes && combinator) nodes = this[combinator](nodes);
2446
- if (!nodes) nodes = root.getElementsByTagName("*");
2447
- return Selector.pseudos[name](nodes, value, root);
2448
- }
2449
- },
2450
-
2451
- pseudos: {
2452
- 'first-child': function(nodes, value, root) {
2453
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
2454
- if (Selector.handlers.previousElementSibling(node)) continue;
2455
- results.push(node);
2456
- }
2457
- return results;
2458
- },
2459
- 'last-child': function(nodes, value, root) {
2460
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
2461
- if (Selector.handlers.nextElementSibling(node)) continue;
2462
- results.push(node);
2463
- }
2464
- return results;
2465
- },
2466
- 'only-child': function(nodes, value, root) {
2467
- var h = Selector.handlers;
2468
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2469
- if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
2470
- results.push(node);
2471
- return results;
2472
- },
2473
- 'nth-child': function(nodes, formula, root) {
2474
- return Selector.pseudos.nth(nodes, formula, root);
2475
- },
2476
- 'nth-last-child': function(nodes, formula, root) {
2477
- return Selector.pseudos.nth(nodes, formula, root, true);
2478
- },
2479
- 'nth-of-type': function(nodes, formula, root) {
2480
- return Selector.pseudos.nth(nodes, formula, root, false, true);
2481
- },
2482
- 'nth-last-of-type': function(nodes, formula, root) {
2483
- return Selector.pseudos.nth(nodes, formula, root, true, true);
2484
- },
2485
- 'first-of-type': function(nodes, formula, root) {
2486
- return Selector.pseudos.nth(nodes, "1", root, false, true);
2487
- },
2488
- 'last-of-type': function(nodes, formula, root) {
2489
- return Selector.pseudos.nth(nodes, "1", root, true, true);
2490
- },
2491
- 'only-of-type': function(nodes, formula, root) {
2492
- var p = Selector.pseudos;
2493
- return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
2494
- },
2495
-
2496
- // handles the an+b logic
2497
- getIndices: function(a, b, total) {
2498
- if (a == 0) return b > 0 ? [b] : [];
2499
- return $R(1, total).inject([], function(memo, i) {
2500
- if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
2501
- return memo;
2502
- });
2503
- },
2504
-
2505
- // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
2506
- nth: function(nodes, formula, root, reverse, ofType) {
2507
- if (nodes.length == 0) return [];
2508
- if (formula == 'even') formula = '2n+0';
2509
- if (formula == 'odd') formula = '2n+1';
2510
- var h = Selector.handlers, results = [], indexed = [], m;
2511
- h.mark(nodes);
2512
- for (var i = 0, node; node = nodes[i]; i++) {
2513
- if (!node.parentNode._counted) {
2514
- h.index(node.parentNode, reverse, ofType);
2515
- indexed.push(node.parentNode);
2516
- }
2517
- }
2518
- if (formula.match(/^\d+$/)) { // just a number
2519
- formula = Number(formula);
2520
- for (var i = 0, node; node = nodes[i]; i++)
2521
- if (node.nodeIndex == formula) results.push(node);
2522
- } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2523
- if (m[1] == "-") m[1] = -1;
2524
- var a = m[1] ? Number(m[1]) : 1;
2525
- var b = m[2] ? Number(m[2]) : 0;
2526
- var indices = Selector.pseudos.getIndices(a, b, nodes.length);
2527
- for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
2528
- for (var j = 0; j < l; j++)
2529
- if (node.nodeIndex == indices[j]) results.push(node);
2530
- }
2531
- }
2532
- h.unmark(nodes);
2533
- h.unmark(indexed);
2534
- return results;
2535
- },
2536
-
2537
- 'empty': function(nodes, value, root) {
2538
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
2539
- // IE treats comments as element nodes
2540
- if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
2541
- results.push(node);
2542
- }
2543
- return results;
2544
- },
2545
-
2546
- 'not': function(nodes, selector, root) {
2547
- var h = Selector.handlers, selectorType, m;
2548
- var exclusions = new Selector(selector).findElements(root);
2549
- h.mark(exclusions);
2550
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2551
- if (!node._counted) results.push(node);
2552
- h.unmark(exclusions);
2553
- return results;
2554
- },
2555
-
2556
- 'enabled': function(nodes, value, root) {
2557
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2558
- if (!node.disabled) results.push(node);
2559
- return results;
2560
- },
2561
-
2562
- 'disabled': function(nodes, value, root) {
2563
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2564
- if (node.disabled) results.push(node);
2565
- return results;
2566
- },
2567
-
2568
- 'checked': function(nodes, value, root) {
2569
- for (var i = 0, results = [], node; node = nodes[i]; i++)
2570
- if (node.checked) results.push(node);
2571
- return results;
2572
- }
2573
- },
2574
-
2575
- operators: {
2576
- '=': function(nv, v) { return nv == v; },
2577
- '!=': function(nv, v) { return nv != v; },
2578
- '^=': function(nv, v) { return nv.startsWith(v); },
2579
- '$=': function(nv, v) { return nv.endsWith(v); },
2580
- '*=': function(nv, v) { return nv.include(v); },
2581
- '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
2582
- '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
2583
- },
2584
-
2585
- matchElements: function(elements, expression) {
2586
- var matches = new Selector(expression).findElements(), h = Selector.handlers;
2587
- h.mark(matches);
2588
- for (var i = 0, results = [], element; element = elements[i]; i++)
2589
- if (element._counted) results.push(element);
2590
- h.unmark(matches);
2591
- return results;
2592
- },
2593
-
2594
- findElement: function(elements, expression, index) {
2595
- if (typeof expression == 'number') {
2596
- index = expression; expression = false;
2597
- }
2598
- return Selector.matchElements(elements, expression || '*')[index || 0];
2599
- },
2600
-
2601
- findChildElements: function(element, expressions) {
2602
- var exprs = expressions.join(','), expressions = [];
2603
- exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
2604
- expressions.push(m[1].strip());
2605
- });
2606
- var results = [], h = Selector.handlers;
2607
- for (var i = 0, l = expressions.length, selector; i < l; i++) {
2608
- selector = new Selector(expressions[i].strip());
2609
- h.concat(results, selector.findElements(element));
2610
- }
2611
- return (l > 1) ? h.unique(results) : results;
2612
- }
2613
- });
2614
-
2615
- function $$() {
2616
- return Selector.findChildElements(document, $A(arguments));
2617
- }
2618
- var Form = {
2619
- reset: function(form) {
2620
- $(form).reset();
2621
- return form;
2622
- },
2623
-
2624
- serializeElements: function(elements, getHash) {
2625
- var data = elements.inject({}, function(result, element) {
2626
- if (!element.disabled && element.name) {
2627
- var key = element.name, value = $(element).getValue();
2628
- if (value != null) {
2629
- if (key in result) {
2630
- if (result[key].constructor != Array) result[key] = [result[key]];
2631
- result[key].push(value);
2632
- }
2633
- else result[key] = value;
2634
- }
2635
- }
2636
- return result;
2637
- });
2638
-
2639
- return getHash ? data : Hash.toQueryString(data);
2640
- }
2641
- };
2642
-
2643
- Form.Methods = {
2644
- serialize: function(form, getHash) {
2645
- return Form.serializeElements(Form.getElements(form), getHash);
2646
- },
2647
-
2648
- getElements: function(form) {
2649
- return $A($(form).getElementsByTagName('*')).inject([],
2650
- function(elements, child) {
2651
- if (Form.Element.Serializers[child.tagName.toLowerCase()])
2652
- elements.push(Element.extend(child));
2653
- return elements;
2654
- }
2655
- );
2656
- },
2657
-
2658
- getInputs: function(form, typeName, name) {
2659
- form = $(form);
2660
- var inputs = form.getElementsByTagName('input');
2661
-
2662
- if (!typeName && !name) return $A(inputs).map(Element.extend);
2663
-
2664
- for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
2665
- var input = inputs[i];
2666
- if ((typeName && input.type != typeName) || (name && input.name != name))
2667
- continue;
2668
- matchingInputs.push(Element.extend(input));
2669
- }
2670
-
2671
- return matchingInputs;
2672
- },
2673
-
2674
- disable: function(form) {
2675
- form = $(form);
2676
- Form.getElements(form).invoke('disable');
2677
- return form;
2678
- },
2679
-
2680
- enable: function(form) {
2681
- form = $(form);
2682
- Form.getElements(form).invoke('enable');
2683
- return form;
2684
- },
2685
-
2686
- findFirstElement: function(form) {
2687
- return $(form).getElements().find(function(element) {
2688
- return element.type != 'hidden' && !element.disabled &&
2689
- ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
2690
- });
2691
- },
2692
-
2693
- focusFirstElement: function(form) {
2694
- form = $(form);
2695
- form.findFirstElement().activate();
2696
- return form;
2697
- },
2698
-
2699
- request: function(form, options) {
2700
- form = $(form), options = Object.clone(options || {});
2701
-
2702
- var params = options.parameters;
2703
- options.parameters = form.serialize(true);
2704
-
2705
- if (params) {
2706
- if (typeof params == 'string') params = params.toQueryParams();
2707
- Object.extend(options.parameters, params);
2708
- }
2709
-
2710
- if (form.hasAttribute('method') && !options.method)
2711
- options.method = form.method;
2712
-
2713
- return new Ajax.Request(form.readAttribute('action'), options);
2714
- }
2715
- }
2716
-
2717
- /*--------------------------------------------------------------------------*/
2718
-
2719
- Form.Element = {
2720
- focus: function(element) {
2721
- $(element).focus();
2722
- return element;
2723
- },
2724
-
2725
- select: function(element) {
2726
- $(element).select();
2727
- return element;
2728
- }
2729
- }
2730
-
2731
- Form.Element.Methods = {
2732
- serialize: function(element) {
2733
- element = $(element);
2734
- if (!element.disabled && element.name) {
2735
- var value = element.getValue();
2736
- if (value != undefined) {
2737
- var pair = {};
2738
- pair[element.name] = value;
2739
- return Hash.toQueryString(pair);
2740
- }
2741
- }
2742
- return '';
2743
- },
2744
-
2745
- getValue: function(element) {
2746
- element = $(element);
2747
- var method = element.tagName.toLowerCase();
2748
- return Form.Element.Serializers[method](element);
2749
- },
2750
-
2751
- clear: function(element) {
2752
- $(element).value = '';
2753
- return element;
2754
- },
2755
-
2756
- present: function(element) {
2757
- return $(element).value != '';
2758
- },
2759
-
2760
- activate: function(element) {
2761
- element = $(element);
2762
- try {
2763
- element.focus();
2764
- if (element.select && (element.tagName.toLowerCase() != 'input' ||
2765
- !['button', 'reset', 'submit'].include(element.type)))
2766
- element.select();
2767
- } catch (e) {}
2768
- return element;
2769
- },
2770
-
2771
- disable: function(element) {
2772
- element = $(element);
2773
- element.blur();
2774
- element.disabled = true;
2775
- return element;
2776
- },
2777
-
2778
- enable: function(element) {
2779
- element = $(element);
2780
- element.disabled = false;
2781
- return element;
2782
- }
2783
- }
2784
-
2785
- /*--------------------------------------------------------------------------*/
2786
-
2787
- var Field = Form.Element;
2788
- var $F = Form.Element.Methods.getValue;
2789
-
2790
- /*--------------------------------------------------------------------------*/
2791
-
2792
- Form.Element.Serializers = {
2793
- input: function(element) {
2794
- switch (element.type.toLowerCase()) {
2795
- case 'checkbox':
2796
- case 'radio':
2797
- return Form.Element.Serializers.inputSelector(element);
2798
- default:
2799
- return Form.Element.Serializers.textarea(element);
2800
- }
2801
- },
2802
-
2803
- inputSelector: function(element) {
2804
- return element.checked ? element.value : null;
2805
- },
2806
-
2807
- textarea: function(element) {
2808
- return element.value;
2809
- },
2810
-
2811
- select: function(element) {
2812
- return this[element.type == 'select-one' ?
2813
- 'selectOne' : 'selectMany'](element);
2814
- },
2815
-
2816
- selectOne: function(element) {
2817
- var index = element.selectedIndex;
2818
- return index >= 0 ? this.optionValue(element.options[index]) : null;
2819
- },
2820
-
2821
- selectMany: function(element) {
2822
- var values, length = element.length;
2823
- if (!length) return null;
2824
-
2825
- for (var i = 0, values = []; i < length; i++) {
2826
- var opt = element.options[i];
2827
- if (opt.selected) values.push(this.optionValue(opt));
2828
- }
2829
- return values;
2830
- },
2831
-
2832
- optionValue: function(opt) {
2833
- // extend element because hasAttribute may not be native
2834
- return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2835
- }
2836
- }
2837
-
2838
- /*--------------------------------------------------------------------------*/
2839
-
2840
- Abstract.TimedObserver = function() {}
2841
- Abstract.TimedObserver.prototype = {
2842
- initialize: function(element, frequency, callback) {
2843
- this.frequency = frequency;
2844
- this.element = $(element);
2845
- this.callback = callback;
2846
-
2847
- this.lastValue = this.getValue();
2848
- this.registerCallback();
2849
- },
2850
-
2851
- registerCallback: function() {
2852
- setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
2853
- },
2854
-
2855
- onTimerEvent: function() {
2856
- var value = this.getValue();
2857
- var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2858
- ? this.lastValue != value : String(this.lastValue) != String(value));
2859
- if (changed) {
2860
- this.callback(this.element, value);
2861
- this.lastValue = value;
2862
- }
2863
- }
2864
- }
2865
-
2866
- Form.Element.Observer = Class.create();
2867
- Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2868
- getValue: function() {
2869
- return Form.Element.getValue(this.element);
2870
- }
2871
- });
2872
-
2873
- Form.Observer = Class.create();
2874
- Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2875
- getValue: function() {
2876
- return Form.serialize(this.element);
2877
- }
2878
- });
2879
-
2880
- /*--------------------------------------------------------------------------*/
2881
-
2882
- Abstract.EventObserver = function() {}
2883
- Abstract.EventObserver.prototype = {
2884
- initialize: function(element, callback) {
2885
- this.element = $(element);
2886
- this.callback = callback;
2887
-
2888
- this.lastValue = this.getValue();
2889
- if (this.element.tagName.toLowerCase() == 'form')
2890
- this.registerFormCallbacks();
2891
- else
2892
- this.registerCallback(this.element);
2893
- },
2894
-
2895
- onElementEvent: function() {
2896
- var value = this.getValue();
2897
- if (this.lastValue != value) {
2898
- this.callback(this.element, value);
2899
- this.lastValue = value;
2900
- }
2901
- },
2902
-
2903
- registerFormCallbacks: function() {
2904
- Form.getElements(this.element).each(this.registerCallback.bind(this));
2905
- },
2906
-
2907
- registerCallback: function(element) {
2908
- if (element.type) {
2909
- switch (element.type.toLowerCase()) {
2910
- case 'checkbox':
2911
- case 'radio':
2912
- Event.observe(element, 'click', this.onElementEvent.bind(this));
2913
- break;
2914
- default:
2915
- Event.observe(element, 'change', this.onElementEvent.bind(this));
2916
- break;
2917
- }
2918
- }
2919
- }
2920
- }
2921
-
2922
- Form.Element.EventObserver = Class.create();
2923
- Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2924
- getValue: function() {
2925
- return Form.Element.getValue(this.element);
2926
- }
2927
- });
2928
-
2929
- Form.EventObserver = Class.create();
2930
- Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2931
- getValue: function() {
2932
- return Form.serialize(this.element);
2933
- }
2934
- });
2935
- if (!window.Event) {
2936
- var Event = new Object();
2937
- }
2938
-
2939
- Object.extend(Event, {
2940
- KEY_BACKSPACE: 8,
2941
- KEY_TAB: 9,
2942
- KEY_RETURN: 13,
2943
- KEY_ESC: 27,
2944
- KEY_LEFT: 37,
2945
- KEY_UP: 38,
2946
- KEY_RIGHT: 39,
2947
- KEY_DOWN: 40,
2948
- KEY_DELETE: 46,
2949
- KEY_HOME: 36,
2950
- KEY_END: 35,
2951
- KEY_PAGEUP: 33,
2952
- KEY_PAGEDOWN: 34,
2953
-
2954
- element: function(event) {
2955
- return $(event.target || event.srcElement);
2956
- },
2957
-
2958
- isLeftClick: function(event) {
2959
- return (((event.which) && (event.which == 1)) ||
2960
- ((event.button) && (event.button == 1)));
2961
- },
2962
-
2963
- pointerX: function(event) {
2964
- return event.pageX || (event.clientX +
2965
- (document.documentElement.scrollLeft || document.body.scrollLeft));
2966
- },
2967
-
2968
- pointerY: function(event) {
2969
- return event.pageY || (event.clientY +
2970
- (document.documentElement.scrollTop || document.body.scrollTop));
2971
- },
2972
-
2973
- stop: function(event) {
2974
- if (event.preventDefault) {
2975
- event.preventDefault();
2976
- event.stopPropagation();
2977
- } else {
2978
- event.returnValue = false;
2979
- event.cancelBubble = true;
2980
- }
2981
- },
2982
-
2983
- // find the first node with the given tagName, starting from the
2984
- // node the event was triggered on; traverses the DOM upwards
2985
- findElement: function(event, tagName) {
2986
- var element = Event.element(event);
2987
- while (element.parentNode && (!element.tagName ||
2988
- (element.tagName.toUpperCase() != tagName.toUpperCase())))
2989
- element = element.parentNode;
2990
- return element;
2991
- },
2992
-
2993
- observers: false,
2994
-
2995
- _observeAndCache: function(element, name, observer, useCapture) {
2996
- if (!this.observers) this.observers = [];
2997
- if (element.addEventListener) {
2998
- this.observers.push([element, name, observer, useCapture]);
2999
- element.addEventListener(name, observer, useCapture);
3000
- } else if (element.attachEvent) {
3001
- this.observers.push([element, name, observer, useCapture]);
3002
- element.attachEvent('on' + name, observer);
3003
- }
3004
- },
3005
-
3006
- unloadCache: function() {
3007
- if (!Event.observers) return;
3008
- for (var i = 0, length = Event.observers.length; i < length; i++) {
3009
- Event.stopObserving.apply(this, Event.observers[i]);
3010
- Event.observers[i][0] = null;
3011
- }
3012
- Event.observers = false;
3013
- },
3014
-
3015
- observe: function(element, name, observer, useCapture) {
3016
- element = $(element);
3017
- useCapture = useCapture || false;
3018
-
3019
- if (name == 'keypress' &&
3020
- (Prototype.Browser.WebKit || element.attachEvent))
3021
- name = 'keydown';
3022
-
3023
- Event._observeAndCache(element, name, observer, useCapture);
3024
- },
3025
-
3026
- stopObserving: function(element, name, observer, useCapture) {
3027
- element = $(element);
3028
- useCapture = useCapture || false;
3029
-
3030
- if (name == 'keypress' &&
3031
- (Prototype.Browser.WebKit || element.attachEvent))
3032
- name = 'keydown';
3033
-
3034
- if (element.removeEventListener) {
3035
- element.removeEventListener(name, observer, useCapture);
3036
- } else if (element.detachEvent) {
3037
- try {
3038
- element.detachEvent('on' + name, observer);
3039
- } catch (e) {}
3040
- }
3041
- }
3042
- });
3043
-
3044
- /* prevent memory leaks in IE */
3045
- if (Prototype.Browser.IE)
3046
- Event.observe(window, 'unload', Event.unloadCache, false);
3047
- var Position = {
3048
- // set to true if needed, warning: firefox performance problems
3049
- // NOT neeeded for page scrolling, only if draggable contained in
3050
- // scrollable elements
3051
- includeScrollOffsets: false,
3052
-
3053
- // must be called before calling withinIncludingScrolloffset, every time the
3054
- // page is scrolled
3055
- prepare: function() {
3056
- this.deltaX = window.pageXOffset
3057
- || document.documentElement.scrollLeft
3058
- || document.body.scrollLeft
3059
- || 0;
3060
- this.deltaY = window.pageYOffset
3061
- || document.documentElement.scrollTop
3062
- || document.body.scrollTop
3063
- || 0;
3064
- },
3065
-
3066
- realOffset: function(element) {
3067
- var valueT = 0, valueL = 0;
3068
- do {
3069
- valueT += element.scrollTop || 0;
3070
- valueL += element.scrollLeft || 0;
3071
- element = element.parentNode;
3072
- } while (element);
3073
- return [valueL, valueT];
3074
- },
3075
-
3076
- cumulativeOffset: function(element) {
3077
- var valueT = 0, valueL = 0;
3078
- do {
3079
- valueT += element.offsetTop || 0;
3080
- valueL += element.offsetLeft || 0;
3081
- element = element.offsetParent;
3082
- } while (element);
3083
- return [valueL, valueT];
3084
- },
3085
-
3086
- positionedOffset: function(element) {
3087
- var valueT = 0, valueL = 0;
3088
- do {
3089
- valueT += element.offsetTop || 0;
3090
- valueL += element.offsetLeft || 0;
3091
- element = element.offsetParent;
3092
- if (element) {
3093
- if(element.tagName=='BODY') break;
3094
- var p = Element.getStyle(element, 'position');
3095
- if (p == 'relative' || p == 'absolute') break;
3096
- }
3097
- } while (element);
3098
- return [valueL, valueT];
3099
- },
3100
-
3101
- offsetParent: function(element) {
3102
- if (element.offsetParent) return element.offsetParent;
3103
- if (element == document.body) return element;
3104
-
3105
- while ((element = element.parentNode) && element != document.body)
3106
- if (Element.getStyle(element, 'position') != 'static')
3107
- return element;
3108
-
3109
- return document.body;
3110
- },
3111
-
3112
- // caches x/y coordinate pair to use with overlap
3113
- within: function(element, x, y) {
3114
- if (this.includeScrollOffsets)
3115
- return this.withinIncludingScrolloffsets(element, x, y);
3116
- this.xcomp = x;
3117
- this.ycomp = y;
3118
- this.offset = this.cumulativeOffset(element);
3119
-
3120
- return (y >= this.offset[1] &&
3121
- y < this.offset[1] + element.offsetHeight &&
3122
- x >= this.offset[0] &&
3123
- x < this.offset[0] + element.offsetWidth);
3124
- },
3125
-
3126
- withinIncludingScrolloffsets: function(element, x, y) {
3127
- var offsetcache = this.realOffset(element);
3128
-
3129
- this.xcomp = x + offsetcache[0] - this.deltaX;
3130
- this.ycomp = y + offsetcache[1] - this.deltaY;
3131
- this.offset = this.cumulativeOffset(element);
3132
-
3133
- return (this.ycomp >= this.offset[1] &&
3134
- this.ycomp < this.offset[1] + element.offsetHeight &&
3135
- this.xcomp >= this.offset[0] &&
3136
- this.xcomp < this.offset[0] + element.offsetWidth);
3137
- },
3138
-
3139
- // within must be called directly before
3140
- overlap: function(mode, element) {
3141
- if (!mode) return 0;
3142
- if (mode == 'vertical')
3143
- return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
3144
- element.offsetHeight;
3145
- if (mode == 'horizontal')
3146
- return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
3147
- element.offsetWidth;
3148
- },
3149
-
3150
- page: function(forElement) {
3151
- var valueT = 0, valueL = 0;
3152
-
3153
- var element = forElement;
3154
- do {
3155
- valueT += element.offsetTop || 0;
3156
- valueL += element.offsetLeft || 0;
3157
-
3158
- // Safari fix
3159
- if (element.offsetParent == document.body)
3160
- if (Element.getStyle(element,'position')=='absolute') break;
3161
-
3162
- } while (element = element.offsetParent);
3163
-
3164
- element = forElement;
3165
- do {
3166
- if (!window.opera || element.tagName=='BODY') {
3167
- valueT -= element.scrollTop || 0;
3168
- valueL -= element.scrollLeft || 0;
3169
- }
3170
- } while (element = element.parentNode);
3171
-
3172
- return [valueL, valueT];
3173
- },
3174
-
3175
- clone: function(source, target) {
3176
- var options = Object.extend({
3177
- setLeft: true,
3178
- setTop: true,
3179
- setWidth: true,
3180
- setHeight: true,
3181
- offsetTop: 0,
3182
- offsetLeft: 0
3183
- }, arguments[2] || {})
3184
-
3185
- // find page position of source
3186
- source = $(source);
3187
- var p = Position.page(source);
3188
-
3189
- // find coordinate system to use
3190
- target = $(target);
3191
- var delta = [0, 0];
3192
- var parent = null;
3193
- // delta [0,0] will do fine with position: fixed elements,
3194
- // position:absolute needs offsetParent deltas
3195
- if (Element.getStyle(target,'position') == 'absolute') {
3196
- parent = Position.offsetParent(target);
3197
- delta = Position.page(parent);
3198
- }
3199
-
3200
- // correct by body offsets (fixes Safari)
3201
- if (parent == document.body) {
3202
- delta[0] -= document.body.offsetLeft;
3203
- delta[1] -= document.body.offsetTop;
3204
- }
3205
-
3206
- // set position
3207
- if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
3208
- if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
3209
- if(options.setWidth) target.style.width = source.offsetWidth + 'px';
3210
- if(options.setHeight) target.style.height = source.offsetHeight + 'px';
3211
- },
3212
-
3213
- absolutize: function(element) {
3214
- element = $(element);
3215
- if (element.style.position == 'absolute') return;
3216
- Position.prepare();
3217
-
3218
- var offsets = Position.positionedOffset(element);
3219
- var top = offsets[1];
3220
- var left = offsets[0];
3221
- var width = element.clientWidth;
3222
- var height = element.clientHeight;
3223
-
3224
- element._originalLeft = left - parseFloat(element.style.left || 0);
3225
- element._originalTop = top - parseFloat(element.style.top || 0);
3226
- element._originalWidth = element.style.width;
3227
- element._originalHeight = element.style.height;
3228
-
3229
- element.style.position = 'absolute';
3230
- element.style.top = top + 'px';
3231
- element.style.left = left + 'px';
3232
- element.style.width = width + 'px';
3233
- element.style.height = height + 'px';
3234
- },
3235
-
3236
- relativize: function(element) {
3237
- element = $(element);
3238
- if (element.style.position == 'relative') return;
3239
- Position.prepare();
3240
-
3241
- element.style.position = 'relative';
3242
- var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
3243
- var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
3244
-
3245
- element.style.top = top + 'px';
3246
- element.style.left = left + 'px';
3247
- element.style.height = element._originalHeight;
3248
- element.style.width = element._originalWidth;
3249
- }
3250
- }
3251
-
3252
- // Safari returns margins on body which is incorrect if the child is absolutely
3253
- // positioned. For performance reasons, redefine Position.cumulativeOffset for
3254
- // KHTML/WebKit only.
3255
- if (Prototype.Browser.WebKit) {
3256
- Position.cumulativeOffset = function(element) {
3257
- var valueT = 0, valueL = 0;
3258
- do {
3259
- valueT += element.offsetTop || 0;
3260
- valueL += element.offsetLeft || 0;
3261
- if (element.offsetParent == document.body)
3262
- if (Element.getStyle(element, 'position') == 'absolute') break;
3263
-
3264
- element = element.offsetParent;
3265
- } while (element);
3266
-
3267
- return [valueL, valueT];
3268
- }
3269
- }
3270
-
3271
- Element.addMethods();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/dev/scriptaculous.js DELETED
@@ -1,58 +0,0 @@
1
- // script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
-
3
- // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
- //
5
- // Permission is hereby granted, free of charge, to any person obtaining
6
- // a copy of this software and associated documentation files (the
7
- // "Software"), to deal in the Software without restriction, including
8
- // without limitation the rights to use, copy, modify, merge, publish,
9
- // distribute, sublicense, and/or sell copies of the Software, and to
10
- // permit persons to whom the Software is furnished to do so, subject to
11
- // the following conditions:
12
- //
13
- // The above copyright notice and this permission notice shall be
14
- // included in all copies or substantial portions of the Software.
15
- //
16
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
- //
24
- // For details, see the script.aculo.us web site: http://script.aculo.us/
25
-
26
- var Scriptaculous = {
27
- Version: '1.7.1_beta3',
28
- require: function(libraryName) {
29
- // inserting via DOM fails in Safari 2.0, so brute force approach
30
- document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
31
- },
32
- REQUIRED_PROTOTYPE: '1.5.1',
33
- load: function() {
34
- function convertVersionString(versionString){
35
- var r = versionString.split('.');
36
- return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
37
- }
38
-
39
- if((typeof Prototype=='undefined') ||
40
- (typeof Element == 'undefined') ||
41
- (typeof Element.Methods=='undefined') ||
42
- (convertVersionString(Prototype.Version) <
43
- convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
44
- throw("script.aculo.us requires the Prototype JavaScript framework >= " +
45
- Scriptaculous.REQUIRED_PROTOTYPE);
46
-
47
- $A(document.getElementsByTagName("script")).findAll( function(s) {
48
- return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
49
- }).each( function(s) {
50
- var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
51
- var includes = s.src.match(/\?.*load=([a-z,]*)/);
52
- (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
53
- function(include) { Scriptaculous.require(path+include+'.js') });
54
- });
55
- }
56
- }
57
-
58
- Scriptaculous.load();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/lib.js CHANGED
@@ -1,207 +1,1228 @@
1
- var Prototype={Version:"1.5.1",Browser:{IE:!!(window.attachEvent&&!window.opera),Opera:!!window.opera,WebKit:navigator.userAgent.indexOf("AppleWebKit/")>-1,Gecko:navigator.userAgent.indexOf("Gecko")>-1&&navigator.userAgent.indexOf("KHTML")==-1},BrowserFeatures:{XPath:!!document.evaluate,ElementExtensions:!!window.HTMLElement,SpecificElementExtensions:document.createElement("div").__proto__!==document.createElement("form").__proto__},ScriptFragment:"<script[^>]*>([\u0001-\uffff]*?)<\/script>",JSONFilter:/^\/\*-secure-\s*(.*)\s*\*\/\s*$/,
2
- emptyFunction:function(){},K:function(a){return a}},Class={create:function(){return function(){this.initialize.apply(this,arguments)}}},Abstract={};Object.extend=function(a,b){for(var c in b)a[c]=b[c];return a};
3
- Object.extend(Object,{inspect:function(a){try{if(a===undefined)return"undefined";if(a===null)return"null";return a.inspect?a.inspect():a.toString()}catch(b){if(b instanceof RangeError)return"...";throw b;}},toJSON:function(a){switch(typeof a){case "undefined":case "function":case "unknown":return;case "boolean":return a.toString()}if(a===null)return"null";if(a.toJSON)return a.toJSON();if(a.ownerDocument!==document){var b=[];for(var c in a){var d=Object.toJSON(a[c]);d!==undefined&&b.push(c.toJSON()+
4
- ": "+d)}return"{"+b.join(", ")+"}"}},keys:function(a){var b=[];for(var c in a)b.push(c);return b},values:function(a){var b=[];for(var c in a)b.push(a[c]);return b},clone:function(a){return Object.extend({},a)}});Function.prototype.bind=function(){var a=this,b=$A(arguments),c=b.shift();return function(){return a.apply(c,b.concat($A(arguments)))}};Function.prototype.bindAsEventListener=function(a){var b=this,c=$A(arguments);a=c.shift();return function(d){return b.apply(a,[d||window.event].concat(c))}};
5
- Object.extend(Number.prototype,{toColorPart:function(){return this.toPaddedString(2,16)},succ:function(){return this+1},times:function(a){$R(0,this,true).each(a);return this},toPaddedString:function(a,b){var c=this.toString(b||10);return"0".times(a-c.length)+c},toJSON:function(){return isFinite(this)?this.toString():"null"}});
6
- Date.prototype.toJSON=function(){return'"'+this.getFullYear()+"-"+(this.getMonth()+1).toPaddedString(2)+"-"+this.getDate().toPaddedString(2)+"T"+this.getHours().toPaddedString(2)+":"+this.getMinutes().toPaddedString(2)+":"+this.getSeconds().toPaddedString(2)+'"'};var Try={these:function(){for(var a,b=0,c=arguments.length;b<c;b++){var d=arguments[b];try{a=d();break}catch(e){}}return a}},PeriodicalExecuter=Class.create();
7
- PeriodicalExecuter.prototype={initialize:function(a,b){this.callback=a;this.frequency=b;this.currentlyExecuting=false;this.registerCallback()},registerCallback:function(){this.timer=setInterval(this.onTimerEvent.bind(this),this.frequency*1E3)},stop:function(){if(this.timer){clearInterval(this.timer);this.timer=null}},onTimerEvent:function(){if(!this.currentlyExecuting)try{this.currentlyExecuting=true;this.callback(this)}finally{this.currentlyExecuting=false}}};
8
- Object.extend(String,{interpret:function(a){return a==null?"":String(a)},specialChar:{"\u0008":"\\b","\t":"\\t","\n":"\\n","\u000c":"\\f","\r":"\\r","\\":"\\\\"}});
9
- Object.extend(String.prototype,{gsub:function(a,b){var c="",d=this,e;for(b=arguments.callee.prepareReplacement(b);d.length>0;)if(e=d.match(a)){c+=d.slice(0,e.index);c+=String.interpret(b(e));d=d.slice(e.index+e[0].length)}else{c+=d;d=""}return c},sub:function(a,b,c){b=this.gsub.prepareReplacement(b);c=c===undefined?1:c;return this.gsub(a,function(d){if(--c<0)return d[0];return b(d)})},scan:function(a,b){this.gsub(a,b);return this},truncate:function(a,b){a=a||30;b=b===undefined?"...":b;return this.length>
10
- a?this.slice(0,a-b.length)+b:this},strip:function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,"")},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,"img"),"")},extractScripts:function(){var a=new RegExp(Prototype.ScriptFragment,"img"),b=new RegExp(Prototype.ScriptFragment,"im");return(this.match(a)||[]).map(function(c){return(c.match(b)||["",""])[1]})},evalScripts:function(){return this.extractScripts().map(function(a){return eval(a)})},
11
- escapeHTML:function(){var a=arguments.callee;a.text.data=this;return a.div.innerHTML},unescapeHTML:function(){var a=document.createElement("div");a.innerHTML=this.stripTags();return a.childNodes[0]?a.childNodes.length>1?$A(a.childNodes).inject("",function(b,c){return b+c.nodeValue}):a.childNodes[0].nodeValue:""},toQueryParams:function(a){var b=this.strip().match(/([^?#]*)(#.*)?$/);if(!b)return{};return b[1].split(a||"&").inject({},function(c,d){if((d=d.split("="))[0]){var e=decodeURIComponent(d.shift()),
12
- f=d.length>1?d.join("="):d[0];if(f!=undefined)f=decodeURIComponent(f);if(e in c){if(c[e].constructor!=Array)c[e]=[c[e]];c[e].push(f)}else c[e]=f}return c})},toArray:function(){return this.split("")},succ:function(){return this.slice(0,this.length-1)+String.fromCharCode(this.charCodeAt(this.length-1)+1)},times:function(a){for(var b="",c=0;c<a;c++)b+=this;return b},camelize:function(){var a=this.split("-"),b=a.length;if(b==1)return a[0];for(var c=this.charAt(0)=="-"?a[0].charAt(0).toUpperCase()+a[0].substring(1):
13
- a[0],d=1;d<b;d++)c+=a[d].charAt(0).toUpperCase()+a[d].substring(1);return c},capitalize:function(){return this.charAt(0).toUpperCase()+this.substring(1).toLowerCase()},underscore:function(){return this.gsub(/::/,"/").gsub(/([A-Z]+)([A-Z][a-z])/,"#{1}_#{2}").gsub(/([a-z\d])([A-Z])/,"#{1}_#{2}").gsub(/-/,"_").toLowerCase()},dasherize:function(){return this.gsub(/_/,"-")},inspect:function(a){var b=this.gsub(/[\x00-\x1f\\]/,function(c){var d=String.specialChar[c[0]];return d?d:"\\u00"+c[0].charCodeAt().toPaddedString(2,
14
- 16)});if(a)return'"'+b.replace(/"/g,'\\"')+'"';return"'"+b.replace(/'/g,"\\'")+"'"},toJSON:function(){return this.inspect(true)},unfilterJSON:function(a){return this.sub(a||Prototype.JSONFilter,"#{1}")},evalJSON:function(a){var b=this.unfilterJSON();try{if(!a||/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(b))return eval("("+b+")")}catch(c){}throw new SyntaxError("Badly formed JSON string: "+this.inspect());},include:function(a){return this.indexOf(a)>-1},startsWith:function(a){return this.indexOf(a)===
15
- 0},endsWith:function(a){var b=this.length-a.length;return b>=0&&this.lastIndexOf(a)===b},empty:function(){return this==""},blank:function(){return/^\s*$/.test(this)}});if(Prototype.Browser.WebKit||Prototype.Browser.IE)Object.extend(String.prototype,{escapeHTML:function(){return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},unescapeHTML:function(){return this.replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">")}});
16
- String.prototype.gsub.prepareReplacement=function(a){if(typeof a=="function")return a;var b=new Template(a);return function(c){return b.evaluate(c)}};String.prototype.parseQuery=String.prototype.toQueryParams;Object.extend(String.prototype.escapeHTML,{div:document.createElement("div"),text:document.createTextNode("")});with(String.prototype.escapeHTML)div.appendChild(text);var Template=Class.create();Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;
17
- Template.prototype={initialize:function(a,b){this.template=a.toString();this.pattern=b||Template.Pattern},evaluate:function(a){return this.template.gsub(this.pattern,function(b){var c=b[1];if(c=="\\")return b[2];return c+String.interpret(a[b[3]])})}};
18
- var $break={},$continue=new Error('"throw $continue" is deprecated, use "return" instead'),Enumerable={each:function(a){var b=0;try{this._each(function(d){a(d,b++)})}catch(c){if(c!=$break)throw c;}return this},eachSlice:function(a,b){for(var c=-a,d=[],e=this.toArray();(c+=a)<e.length;)d.push(e.slice(c,c+a));return d.map(b)},all:function(a){var b=true;this.each(function(c,d){b=b&&!!(a||Prototype.K)(c,d);if(!b)throw $break;});return b},any:function(a){var b=false;this.each(function(c,d){if(b=!!(a||
19
- Prototype.K)(c,d))throw $break;});return b},collect:function(a){var b=[];this.each(function(c,d){b.push((a||Prototype.K)(c,d))});return b},detect:function(a){var b;this.each(function(c,d){if(a(c,d)){b=c;throw $break;}});return b},findAll:function(a){var b=[];this.each(function(c,d){a(c,d)&&b.push(c)});return b},grep:function(a,b){var c=[];this.each(function(d,e){if(d.toString().match(a))c.push((b||Prototype.K)(d,e))});return c},include:function(a){var b=false;this.each(function(c){if(c==a){b=true;
20
- throw $break;}});return b},inGroupsOf:function(a,b){b=b===undefined?null:b;return this.eachSlice(a,function(c){for(;c.length<a;)c.push(b);return c})},inject:function(a,b){this.each(function(c,d){a=b(a,c,d)});return a},invoke:function(a){var b=$A(arguments).slice(1);return this.map(function(c){return c[a].apply(c,b)})},max:function(a){var b;this.each(function(c,d){c=(a||Prototype.K)(c,d);if(b==undefined||c>=b)b=c});return b},min:function(a){var b;this.each(function(c,d){c=(a||Prototype.K)(c,d);if(b==
21
- undefined||c<b)b=c});return b},partition:function(a){var b=[],c=[];this.each(function(d,e){((a||Prototype.K)(d,e)?b:c).push(d)});return[b,c]},pluck:function(a){var b=[];this.each(function(c){b.push(c[a])});return b},reject:function(a){var b=[];this.each(function(c,d){a(c,d)||b.push(c)});return b},sortBy:function(a){return this.map(function(b,c){return{value:b,criteria:a(b,c)}}).sort(function(b,c){var d=b.criteria,e=c.criteria;return d<e?-1:d>e?1:0}).pluck("value")},toArray:function(){return this.map()},
22
- zip:function(){var a=Prototype.K,b=$A(arguments);if(typeof b.last()=="function")a=b.pop();var c=[this].concat(b).map($A);return this.map(function(d,e){return a(c.pluck(e))})},size:function(){return this.toArray().length},inspect:function(){return"#<Enumerable:"+this.toArray().inspect()+">"}};Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray});
23
- var $A=Array.from=function(a){if(!a)return[];if(a.toArray)return a.toArray();else{for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b}};if(Prototype.Browser.WebKit)$A=Array.from=function(a){if(!a)return[];if(!(typeof a=="function"&&a=="[object NodeList]")&&a.toArray)return a.toArray();else{for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b}};Object.extend(Array.prototype,Enumerable);if(!Array.prototype._reverse)Array.prototype._reverse=Array.prototype.reverse;
24
- Object.extend(Array.prototype,{_each:function(a){for(var b=0,c=this.length;b<c;b++)a(this[b])},clear:function(){this.length=0;return this},first:function(){return this[0]},last:function(){return this[this.length-1]},compact:function(){return this.select(function(a){return a!=null})},flatten:function(){return this.inject([],function(a,b){return a.concat(b&&b.constructor==Array?b.flatten():[b])})},without:function(){var a=$A(arguments);return this.select(function(b){return!a.include(b)})},indexOf:function(a){for(var b=
25
- 0,c=this.length;b<c;b++)if(this[b]==a)return b;return-1},reverse:function(a){return(a!==false?this:this.toArray())._reverse()},reduce:function(){return this.length>1?this:this[0]},uniq:function(a){return this.inject([],function(b,c,d){if(0==d||(a?b.last()!=c:!b.include(c)))b.push(c);return b})},clone:function(){return[].concat(this)},size:function(){return this.length},inspect:function(){return"["+this.map(Object.inspect).join(", ")+"]"},toJSON:function(){var a=[];this.each(function(b){b=Object.toJSON(b);
26
- b!==undefined&&a.push(b)});return"["+a.join(", ")+"]"}});Array.prototype.toArray=Array.prototype.clone;function $w(a){return(a=a.strip())?a.split(/\s+/):[]}if(Prototype.Browser.Opera)Array.prototype.concat=function(){for(var a=[],b=0,c=this.length;b<c;b++)a.push(this[b]);b=0;for(c=arguments.length;b<c;b++)if(arguments[b].constructor==Array)for(var d=0,e=arguments[b].length;d<e;d++)a.push(arguments[b][d]);else a.push(arguments[b]);return a};
27
- var Hash=function(a){a instanceof Hash?this.merge(a):Object.extend(this,a||{})};
28
- Object.extend(Hash,{toQueryString:function(a){var b=[];b.add=arguments.callee.addPair;this.prototype._each.call(a,function(c){if(c.key){var d=c.value;if(d&&typeof d=="object")d.constructor==Array&&d.each(function(e){b.add(c.key,e)});else b.add(c.key,d)}});return b.join("&")},toJSON:function(a){var b=[];this.prototype._each.call(a,function(c){var d=Object.toJSON(c.value);d!==undefined&&b.push(c.key.toJSON()+": "+d)});return"{"+b.join(", ")+"}"}});
29
- Hash.toQueryString.addPair=function(a,b){a=encodeURIComponent(a);b===undefined?this.push(a):this.push(a+"="+(b==null?"":encodeURIComponent(b)))};Object.extend(Hash.prototype,Enumerable);
30
- Object.extend(Hash.prototype,{_each:function(a){for(var b in this){var c=this[b];if(!(c&&c==Hash.prototype[b])){var d=[b,c];d.key=b;d.value=c;a(d)}}},keys:function(){return this.pluck("key")},values:function(){return this.pluck("value")},merge:function(a){return $H(a).inject(this,function(b,c){b[c.key]=c.value;return b})},remove:function(){for(var a,b=0,c=arguments.length;b<c;b++){var d=this[arguments[b]];if(d!==undefined)if(a===undefined)a=d;else{if(a.constructor!=Array)a=[a];a.push(d)}delete this[arguments[b]]}return a},
31
- toQueryString:function(){return Hash.toQueryString(this)},inspect:function(){return"#<Hash:{"+this.map(function(a){return a.map(Object.inspect).join(": ")}).join(", ")+"}>"},toJSON:function(){return Hash.toJSON(this)}});function $H(a){if(a instanceof Hash)return a;return new Hash(a)}
32
- if(function(){var a=0,b=function(d){this.key=d};b.prototype.key="foo";for(var c in new b("bar"))a++;return a>1}())Hash.prototype._each=function(a){var b=[];for(var c in this){var d=this[c];if(!(d&&d==Hash.prototype[c]||b.include(c))){b.push(c);var e=[c,d];e.key=c;e.value=d;a(e)}}};ObjectRange=Class.create();Object.extend(ObjectRange.prototype,Enumerable);
33
- Object.extend(ObjectRange.prototype,{initialize:function(a,b,c){this.start=a;this.end=b;this.exclusive=c},_each:function(a){for(var b=this.start;this.include(b);){a(b);b=b.succ()}},include:function(a){if(a<this.start)return false;if(this.exclusive)return a<this.end;return a<=this.end}});
34
- var $R=function(a,b,c){return new ObjectRange(a,b,c)},Ajax={getTransport:function(){return Try.these(function(){return new XMLHttpRequest},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")})||false},activeRequestCount:0};
35
- Ajax.Responders={responders:[],_each:function(a){this.responders._each(a)},register:function(a){this.include(a)||this.responders.push(a)},unregister:function(a){this.responders=this.responders.without(a)},dispatch:function(a,b,c,d){this.each(function(e){if(typeof e[a]=="function")try{e[a].apply(e,[b,c,d])}catch(f){}})}};Object.extend(Ajax.Responders,Enumerable);Ajax.Responders.register({onCreate:function(){Ajax.activeRequestCount++},onComplete:function(){Ajax.activeRequestCount--}});Ajax.Base=function(){};
36
- Ajax.Base.prototype={setOptions:function(a){this.options={method:"post",asynchronous:true,contentType:"application/x-www-form-urlencoded",encoding:"UTF-8",parameters:""};Object.extend(this.options,a||{});this.options.method=this.options.method.toLowerCase();if(typeof this.options.parameters=="string")this.options.parameters=this.options.parameters.toQueryParams()}};Ajax.Request=Class.create();Ajax.Request.Events=["Uninitialized","Loading","Loaded","Interactive","Complete"];
37
- Ajax.Request.prototype=Object.extend(new Ajax.Base,{_complete:false,initialize:function(a,b){this.transport=Ajax.getTransport();this.setOptions(b);this.request(a)},request:function(a){this.url=a;this.method=this.options.method;a=Object.clone(this.options.parameters);if(!["get","post"].include(this.method)){a._method=this.method;this.method="post"}this.parameters=a;if(a=Hash.toQueryString(a))if(this.method=="get")this.url+=(this.url.include("?")?"&":"?")+a;else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))a+=
38
- "&_=";try{this.options.onCreate&&this.options.onCreate(this.transport);Ajax.Responders.dispatch("onCreate",this,this.transport);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);this.options.asynchronous&&setTimeout(function(){this.respondToReadyState(1)}.bind(this),10);this.transport.onreadystatechange=this.onStateChange.bind(this);this.setRequestHeaders();this.body=this.method=="post"?this.options.postBody||a:null;this.transport.send(this.body);!this.options.asynchronous&&
39
- this.transport.overrideMimeType&&this.onStateChange()}catch(b){this.dispatchException(b)}},onStateChange:function(){var a=this.transport.readyState;a>1&&!(a==4&&this._complete)&&this.respondToReadyState(this.transport.readyState)},setRequestHeaders:function(){var a={"X-Requested-With":"XMLHttpRequest","X-Prototype-Version":Prototype.Version,Accept:"text/javascript, text/html, application/xml, text/xml, */*"};if(this.method=="post"){a["Content-type"]=this.options.contentType+(this.options.encoding?
40
- "; charset="+this.options.encoding:"");if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005)a.Connection="close"}if(typeof this.options.requestHeaders=="object"){var b=this.options.requestHeaders;if(typeof b.push=="function")for(var c=0,d=b.length;c<d;c+=2)a[b[c]]=b[c+1];else $H(b).each(function(f){a[f.key]=f.value})}for(var e in a)this.transport.setRequestHeader(e,a[e])},success:function(){return!this.transport.status||this.transport.status>=200&&this.transport.status<
41
- 300},respondToReadyState:function(a){a=Ajax.Request.Events[a];var b=this.transport,c=this.evalJSON();if(a=="Complete"){try{this._complete=true;(this.options["on"+this.transport.status]||this.options["on"+(this.success()?"Success":"Failure")]||Prototype.emptyFunction)(b,c)}catch(d){this.dispatchException(d)}var e=this.getHeader("Content-type");e&&e.strip().match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)&&this.evalResponse()}try{(this.options["on"+a]||Prototype.emptyFunction)(b,c);Ajax.Responders.dispatch("on"+
42
- a,this,b,c)}catch(f){this.dispatchException(f)}if(a=="Complete")this.transport.onreadystatechange=Prototype.emptyFunction},getHeader:function(a){try{return this.transport.getResponseHeader(a)}catch(b){return null}},evalJSON:function(){try{var a=this.getHeader("X-JSON");return a?a.evalJSON():null}catch(b){return null}},evalResponse:function(){try{return eval((this.transport.responseText||"").unfilterJSON())}catch(a){this.dispatchException(a)}},dispatchException:function(a){(this.options.onException||
43
- Prototype.emptyFunction)(this,a);Ajax.Responders.dispatch("onException",this,a)}});Ajax.Updater=Class.create();
44
- Object.extend(Object.extend(Ajax.Updater.prototype,Ajax.Request.prototype),{initialize:function(a,b,c){this.container={success:a.success||a,failure:a.failure||(a.success?null:a)};this.transport=Ajax.getTransport();this.setOptions(c);var d=this.options.onComplete||Prototype.emptyFunction;this.options.onComplete=function(e,f){this.updateContent();d(e,f)}.bind(this);this.request(b)},updateContent:function(){var a=this.container[this.success()?"success":"failure"],b=this.transport.responseText;this.options.evalScripts||
45
- (b=b.stripScripts());if(a=$(a))if(this.options.insertion)new this.options.insertion(a,b);else a.update(b);this.success()&&this.onComplete&&setTimeout(this.onComplete.bind(this),10)}});Ajax.PeriodicalUpdater=Class.create();
46
- Ajax.PeriodicalUpdater.prototype=Object.extend(new Ajax.Base,{initialize:function(a,b,c){this.setOptions(c);this.onComplete=this.options.onComplete;this.frequency=this.options.frequency||2;this.decay=this.options.decay||1;this.updater={};this.container=a;this.url=b;this.start()},start:function(){this.options.onComplete=this.updateComplete.bind(this);this.onTimerEvent()},stop:function(){this.updater.options.onComplete=undefined;clearTimeout(this.timer);(this.onComplete||Prototype.emptyFunction).apply(this,
47
- arguments)},updateComplete:function(a){if(this.options.decay){this.decay=a.responseText==this.lastText?this.decay*this.options.decay:1;this.lastText=a.responseText}this.timer=setTimeout(this.onTimerEvent.bind(this),this.decay*this.frequency*1E3)},onTimerEvent:function(){this.updater=new Ajax.Updater(this.container,this.url,this.options)}});
48
- function $(a){if(arguments.length>1){for(var b=0,c=[],d=arguments.length;b<d;b++)c.push($(arguments[b]));return c}if(typeof a=="string")a=document.getElementById(a);return Element.extend(a)}
49
- if(Prototype.BrowserFeatures.XPath){document._getElementsByXPath=function(a,b){for(var c=[],d=document.evaluate(a,$(b)||document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null),e=0,f=d.snapshotLength;e<f;e++)c.push(d.snapshotItem(e));return c};document.getElementsByClassName=function(a,b){return document._getElementsByXPath(".//*[contains(concat(' ', @class, ' '), ' "+a+" ')]",b)}}else document.getElementsByClassName=function(a,b){for(var c=($(b)||document.body).getElementsByTagName("*"),d=[],e,
50
- f=0,g=c.length;f<g;f++){e=c[f];Element.hasClassName(e,a)&&d.push(Element.extend(e))}return d};if(!window.Element)var Element={};
51
- Element.extend=function(a){var b=Prototype.BrowserFeatures;if(!a||!a.tagName||a.nodeType==3||a._extended||b.SpecificElementExtensions||a==window)return a;var c={},d=a.tagName,e=Element.extend.cache,f=Element.Methods.ByTag;if(!b.ElementExtensions){Object.extend(c,Element.Methods);Object.extend(c,Element.Methods.Simulated)}f[d]&&Object.extend(c,f[d]);for(var g in c){b=c[g];if(typeof b=="function"&&!(g in a))a[g]=e.findOrStore(b)}a._extended=Prototype.emptyFunction;return a};
52
- Element.extend.cache={findOrStore:function(a){return this[a]=this[a]||function(){return a.apply(null,[this].concat($A(arguments)))}}};
53
- Element.Methods={visible:function(a){return $(a).style.display!="none"},toggle:function(a){a=$(a);Element[Element.visible(a)?"hide":"show"](a);return a},hide:function(a){$(a).style.display="none";return a},show:function(a){$(a).style.display="";return a},remove:function(a){a=$(a);a.parentNode.removeChild(a);return a},update:function(a,b){b=typeof b=="undefined"?"":b.toString();$(a).innerHTML=b.stripScripts();setTimeout(function(){b.evalScripts()},10);return a},replace:function(a,b){a=$(a);b=typeof b==
54
- "undefined"?"":b.toString();if(a.outerHTML)a.outerHTML=b.stripScripts();else{var c=a.ownerDocument.createRange();c.selectNodeContents(a);a.parentNode.replaceChild(c.createContextualFragment(b.stripScripts()),a)}setTimeout(function(){b.evalScripts()},10);return a},inspect:function(a){a=$(a);var b="<"+a.tagName.toLowerCase();$H({id:"id",className:"class"}).each(function(c){var d=c.first();c=c.last();if(d=(a[d]||"").toString())b+=" "+c+"="+d.inspect(true)});return b+">"},recursivelyCollect:function(a,
55
- b){a=$(a);for(var c=[];a=a[b];)a.nodeType==1&&c.push(Element.extend(a));return c},ancestors:function(a){return $(a).recursivelyCollect("parentNode")},descendants:function(a){return $A($(a).getElementsByTagName("*")).each(Element.extend)},firstDescendant:function(a){for(a=$(a).firstChild;a&&a.nodeType!=1;)a=a.nextSibling;return $(a)},immediateDescendants:function(a){if(!(a=$(a).firstChild))return[];for(;a&&a.nodeType!=1;)a=a.nextSibling;if(a)return[a].concat($(a).nextSiblings());return[]},previousSiblings:function(a){return $(a).recursivelyCollect("previousSibling")},
56
- nextSiblings:function(a){return $(a).recursivelyCollect("nextSibling")},siblings:function(a){a=$(a);return a.previousSiblings().reverse().concat(a.nextSiblings())},match:function(a,b){if(typeof b=="string")b=new Selector(b);return b.match($(a))},up:function(a,b,c){a=$(a);if(arguments.length==1)return $(a.parentNode);var d=a.ancestors();return b?Selector.findElement(d,b,c):d[c||0]},down:function(a,b,c){a=$(a);if(arguments.length==1)return a.firstDescendant();var d=a.descendants();return b?Selector.findElement(d,
57
- b,c):d[c||0]},previous:function(a,b,c){a=$(a);if(arguments.length==1)return $(Selector.handlers.previousElementSibling(a));var d=a.previousSiblings();return b?Selector.findElement(d,b,c):d[c||0]},next:function(a,b,c){a=$(a);if(arguments.length==1)return $(Selector.handlers.nextElementSibling(a));var d=a.nextSiblings();return b?Selector.findElement(d,b,c):d[c||0]},getElementsBySelector:function(){var a=$A(arguments),b=$(a.shift());return Selector.findChildElements(b,a)},getElementsByClassName:function(a,
58
- b){return document.getElementsByClassName(b,a)},readAttribute:function(a,b){a=$(a);if(Prototype.Browser.IE){if(!a.attributes)return null;var c=Element._attributeTranslations;if(c.values[b])return c.values[b](a,b);if(c.names[b])b=c.names[b];return(c=a.attributes[b])?c.nodeValue:null}return a.getAttribute(b)},getHeight:function(a){return $(a).getDimensions().height},getWidth:function(a){return $(a).getDimensions().width},classNames:function(a){return new Element.ClassNames(a)},hasClassName:function(a,
59
- b){if(a=$(a)){var c=a.className;if(c.length==0)return false;if(c==b||c.match(new RegExp("(^|\\s)"+b+"(\\s|$)")))return true;return false}},addClassName:function(a,b){if(a=$(a)){Element.classNames(a).add(b);return a}},removeClassName:function(a,b){if(a=$(a)){Element.classNames(a).remove(b);return a}},toggleClassName:function(a,b){if(a=$(a)){Element.classNames(a)[a.hasClassName(b)?"remove":"add"](b);return a}},observe:function(){Event.observe.apply(Event,arguments);return $A(arguments).first()},stopObserving:function(){Event.stopObserving.apply(Event,
60
- arguments);return $A(arguments).first()},cleanWhitespace:function(a){a=$(a);for(var b=a.firstChild;b;){var c=b.nextSibling;b.nodeType==3&&!/\S/.test(b.nodeValue)&&a.removeChild(b);b=c}return a},empty:function(a){return $(a).innerHTML.blank()},descendantOf:function(a,b){a=$(a);for(b=$(b);a=a.parentNode;)if(a==b)return true;return false},scrollTo:function(a){a=$(a);var b=Position.cumulativeOffset(a);window.scrollTo(b[0],b[1]);return a},getStyle:function(a,b){a=$(a);b=b=="float"?"cssFloat":b.camelize();
61
- var c=a.style[b];if(!c)c=(c=document.defaultView.getComputedStyle(a,null))?c[b]:null;if(b=="opacity")return c?parseFloat(c):1;return c=="auto"?null:c},getOpacity:function(a){return $(a).getStyle("opacity")},setStyle:function(a,b,c){a=$(a);var d=a.style;for(var e in b)if(e=="opacity")a.setOpacity(b[e]);else d[e=="float"||e=="cssFloat"?d.styleFloat===undefined?"cssFloat":"styleFloat":c?e:e.camelize()]=b[e];return a},setOpacity:function(a,b){a=$(a);a.style.opacity=b==1||b===""?"":b<1.0E-5?0:b;return a},
62
- getDimensions:function(a){a=$(a);var b=$(a).getStyle("display");if(b!="none"&&b!=null)return{width:a.offsetWidth,height:a.offsetHeight};b=a.style;var c=b.visibility,d=b.position,e=b.display;b.visibility="hidden";b.position="absolute";b.display="block";var f=a.clientWidth;a=a.clientHeight;b.display=e;b.position=d;b.visibility=c;return{width:f,height:a}},makePositioned:function(a){a=$(a);var b=Element.getStyle(a,"position");if(b=="static"||!b){a._madePositioned=true;a.style.position="relative";if(window.opera){a.style.top=
63
- 0;a.style.left=0}}return a},undoPositioned:function(a){a=$(a);if(a._madePositioned){a._madePositioned=undefined;a.style.position=a.style.top=a.style.left=a.style.bottom=a.style.right=""}return a},makeClipping:function(a){a=$(a);if(a._overflow)return a;a._overflow=a.style.overflow||"auto";if((Element.getStyle(a,"overflow")||"visible")!="hidden")a.style.overflow="hidden";return a},undoClipping:function(a){a=$(a);if(!a._overflow)return a;a.style.overflow=a._overflow=="auto"?"":a._overflow;a._overflow=
64
- null;return a}};Object.extend(Element.Methods,{childOf:Element.Methods.descendantOf,childElements:Element.Methods.immediateDescendants});
65
- if(Prototype.Browser.Opera){Element.Methods._getStyle=Element.Methods.getStyle;Element.Methods.getStyle=function(a,b){switch(b){case "left":case "top":case "right":case "bottom":if(Element._getStyle(a,"position")=="static")return null;default:return Element._getStyle(a,b)}}}else if(Prototype.Browser.IE){Element.Methods.getStyle=function(a,b){a=$(a);b=b=="float"||b=="cssFloat"?"styleFloat":b.camelize();var c=a.style[b];if(!c&&a.currentStyle)c=a.currentStyle[b];if(b=="opacity"){if(c=(a.getStyle("filter")||
66
- "").match(/alpha\(opacity=(.*)\)/))if(c[1])return parseFloat(c[1])/100;return 1}if(c=="auto"){if((b=="width"||b=="height")&&a.getStyle("display")!="none")return a["offset"+b.capitalize()]+"px";return null}return c};Element.Methods.setOpacity=function(a,b){a=$(a);var c=a.getStyle("filter"),d=a.style;if(b==1||b===""){d.filter=c.replace(/alpha\([^\)]*\)/gi,"");return a}else if(b<1.0E-5)b=0;d.filter=c.replace(/alpha\([^\)]*\)/gi,"")+"alpha(opacity="+b*100+")";return a};Element.Methods.update=function(a,
67
- b){a=$(a);b=typeof b=="undefined"?"":b.toString();var c=a.tagName.toUpperCase();if(["THEAD","TBODY","TR","TD"].include(c)){var d=document.createElement("div");switch(c){case "THEAD":case "TBODY":d.innerHTML="<table><tbody>"+b.stripScripts()+"</tbody></table>";depth=2;break;case "TR":d.innerHTML="<table><tbody><tr>"+b.stripScripts()+"</tr></tbody></table>";depth=3;break;case "TD":d.innerHTML="<table><tbody><tr><td>"+b.stripScripts()+"</td></tr></tbody></table>";depth=4}$A(a.childNodes).each(function(e){a.removeChild(e)});
68
- depth.times(function(){d=d.firstChild});$A(d.childNodes).each(function(e){a.appendChild(e)})}else a.innerHTML=b.stripScripts();setTimeout(function(){b.evalScripts()},10);return a}}else if(Prototype.Browser.Gecko)Element.Methods.setOpacity=function(a,b){a=$(a);a.style.opacity=b==1?0.999999:b===""?"":b<1.0E-5?0:b;return a};
69
- Element._attributeTranslations={names:{colspan:"colSpan",rowspan:"rowSpan",valign:"vAlign",datetime:"dateTime",accesskey:"accessKey",tabindex:"tabIndex",enctype:"encType",maxlength:"maxLength",readonly:"readOnly",longdesc:"longDesc"},values:{_getAttr:function(a,b){return a.getAttribute(b,2)},_flag:function(a,b){return $(a).hasAttribute(b)?b:null},style:function(a){return a.style.cssText.toLowerCase()},title:function(a){a=a.getAttributeNode("title");return a.specified?a.nodeValue:null}}};
70
- (function(){Object.extend(this,{href:this._getAttr,src:this._getAttr,type:this._getAttr,disabled:this._flag,checked:this._flag,readonly:this._flag,multiple:this._flag})}).call(Element._attributeTranslations.values);Element.Methods.Simulated={hasAttribute:function(a,b){var c;b=Element._attributeTranslations.names[b]||b;return(c=$(a).getAttributeNode(b))&&c.specified}};Element.Methods.ByTag={};Object.extend(Element,Element.Methods);
71
- if(!Prototype.BrowserFeatures.ElementExtensions&&document.createElement("div").__proto__){window.HTMLElement={};window.HTMLElement.prototype=document.createElement("div").__proto__;Prototype.BrowserFeatures.ElementExtensions=true}Element.hasAttribute=function(a,b){if(a.hasAttribute)return a.hasAttribute(b);return Element.Methods.Simulated.hasAttribute(a,b)};
72
- Element.addMethods=function(a){function b(i){i=i.toUpperCase();Element.Methods.ByTag[i]||(Element.Methods.ByTag[i]={});Object.extend(Element.Methods.ByTag[i],a)}function c(i,j,k){var m=Element.extend.cache;for(var l in i){var n=i[l];if(!(k||false)||!(l in j))j[l]=m.findOrStore(n)}}function d(i){var j,k={OPTGROUP:"OptGroup",TEXTAREA:"TextArea",P:"Paragraph",FIELDSET:"FieldSet",UL:"UList",OL:"OList",DL:"DList",DIR:"Directory",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading",
73
- Q:"Quote",INS:"Mod",DEL:"Mod",A:"Anchor",IMG:"Image",CAPTION:"TableCaption",COL:"TableCol",COLGROUP:"TableCol",THEAD:"TableSection",TFOOT:"TableSection",TBODY:"TableSection",TR:"TableRow",TH:"TableCell",TD:"TableCell",FRAMESET:"FrameSet",IFRAME:"IFrame"};if(k[i])j="HTML"+k[i]+"Element";if(window[j])return window[j];j="HTML"+i+"Element";if(window[j])return window[j];j="HTML"+i.capitalize()+"Element";if(window[j])return window[j];window[j]={};window[j].prototype=document.createElement(i).__proto__;
74
- return window[j]}var e=Prototype.BrowserFeatures,f=Element.Methods.ByTag;if(!a){Object.extend(Form,Form.Methods);Object.extend(Form.Element,Form.Element.Methods);Object.extend(Element.Methods.ByTag,{FORM:Object.clone(Form.Methods),INPUT:Object.clone(Form.Element.Methods),SELECT:Object.clone(Form.Element.Methods),TEXTAREA:Object.clone(Form.Element.Methods)})}if(arguments.length==2){var g=a;a=arguments[1]}if(g)g.constructor==Array?g.each(b):b(g);else Object.extend(Element.Methods,a||{});if(e.ElementExtensions){c(Element.Methods,
75
- HTMLElement.prototype);c(Element.Methods.Simulated,HTMLElement.prototype,true)}if(e.SpecificElementExtensions)for(var h in Element.Methods.ByTag){e=d(h);typeof e!="undefined"&&c(f[h],e.prototype)}Object.extend(Element,Element.Methods);delete Element.ByTag};var Toggle={display:Element.toggle};Abstract.Insertion=function(a){this.adjacency=a};
76
- Abstract.Insertion.prototype={initialize:function(a,b){this.element=$(a);this.content=b.stripScripts();if(this.adjacency&&this.element.insertAdjacentHTML)try{this.element.insertAdjacentHTML(this.adjacency,this.content)}catch(c){if(["TBODY","TR"].include(this.element.tagName.toUpperCase()))this.insertContent(this.contentFromAnonymousTable());else throw c;}else{this.range=this.element.ownerDocument.createRange();this.initializeRange&&this.initializeRange();this.insertContent([this.range.createContextualFragment(this.content)])}setTimeout(function(){b.evalScripts()},
77
- 10)},contentFromAnonymousTable:function(){var a=document.createElement("div");a.innerHTML="<table><tbody>"+this.content+"</tbody></table>";return $A(a.childNodes[0].childNodes[0].childNodes)}};var Insertion={};Insertion.Before=Class.create();Insertion.Before.prototype=Object.extend(new Abstract.Insertion("beforeBegin"),{initializeRange:function(){this.range.setStartBefore(this.element)},insertContent:function(a){a.each(function(b){this.element.parentNode.insertBefore(b,this.element)}.bind(this))}});
78
- Insertion.Top=Class.create();Insertion.Top.prototype=Object.extend(new Abstract.Insertion("afterBegin"),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(true)},insertContent:function(a){a.reverse(false).each(function(b){this.element.insertBefore(b,this.element.firstChild)}.bind(this))}});Insertion.Bottom=Class.create();
79
- Insertion.Bottom.prototype=Object.extend(new Abstract.Insertion("beforeEnd"),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(this.element)},insertContent:function(a){a.each(function(b){this.element.appendChild(b)}.bind(this))}});Insertion.After=Class.create();
80
- Insertion.After.prototype=Object.extend(new Abstract.Insertion("afterEnd"),{initializeRange:function(){this.range.setStartAfter(this.element)},insertContent:function(a){a.each(function(b){this.element.parentNode.insertBefore(b,this.element.nextSibling)}.bind(this))}});Element.ClassNames=Class.create();
81
- Element.ClassNames.prototype={initialize:function(a){this.element=$(a)},_each:function(a){this.element.className.split(/\s+/).select(function(b){return b.length>0})._each(a)},set:function(a){this.element.className=a},add:function(a){this.include(a)||this.set($A(this).concat(a).join(" "))},remove:function(a){this.include(a)&&this.set($A(this).without(a).join(" "))},toString:function(){return $A(this).join(" ")}};Object.extend(Element.ClassNames.prototype,Enumerable);var Selector=Class.create();
82
- Selector.prototype={initialize:function(a){this.expression=a.strip();this.compileMatcher()},compileMatcher:function(){if(Prototype.BrowserFeatures.XPath&&!/\[[\w-]*?:/.test(this.expression))return this.compileXPathMatcher();var a=this.expression,b=Selector.patterns,c=Selector.criteria,d,e;if(Selector._cache[a])this.matcher=Selector._cache[a];else{for(this.matcher=["this.matcher = function(root) {","var r = root, h = Selector.handlers, c = false, n;"];a&&d!=a&&/\S/.test(a);){d=a;for(var f in b){e=
83
- b[f];if(e=a.match(e)){this.matcher.push(typeof c[f]=="function"?c[f](e):(new Template(c[f])).evaluate(e));a=a.replace(e[0],"");break}}}this.matcher.push("return h.unique(n);\n}");eval(this.matcher.join("\n"));Selector._cache[this.expression]=this.matcher}},compileXPathMatcher:function(){var a=this.expression,b=Selector.patterns,c=Selector.xpath,d,e;if(Selector._cache[a])this.xpath=Selector._cache[a];else{for(this.matcher=[".//*"];a&&d!=a&&/\S/.test(a);){d=a;for(var f in b)if(e=a.match(b[f])){this.matcher.push(typeof c[f]==
84
- "function"?c[f](e):(new Template(c[f])).evaluate(e));a=a.replace(e[0],"");break}}this.xpath=this.matcher.join("");Selector._cache[this.expression]=this.xpath}},findElements:function(a){a=a||document;if(this.xpath)return document._getElementsByXPath(this.xpath,a);return this.matcher(a)},match:function(a){return this.findElements(document).include(a)},toString:function(){return this.expression},inspect:function(){return"#<Selector:"+this.expression.inspect()+">"}};
85
- Object.extend(Selector,{_cache:{},xpath:{descendant:"//*",child:"/*",adjacent:"/following-sibling::*[1]",laterSibling:"/following-sibling::*",tagName:function(a){if(a[1]=="*")return"";return"[local-name()='"+a[1].toLowerCase()+"' or local-name()='"+a[1].toUpperCase()+"']"},className:"[contains(concat(' ', @class, ' '), ' #{1} ')]",id:"[@id='#{1}']",attrPresence:"[@#{1}]",attr:function(a){a[3]=a[5]||a[6];return(new Template(Selector.xpath.operators[a[2]])).evaluate(a)},pseudo:function(a){var b=Selector.xpath.pseudos[a[1]];
86
- if(!b)return"";if(typeof b==="function")return b(a);return(new Template(Selector.xpath.pseudos[a[1]])).evaluate(a)},operators:{"=":"[@#{1}='#{3}']","!=":"[@#{1}!='#{3}']","^=":"[starts-with(@#{1}, '#{3}')]","$=":"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']","*=":"[contains(@#{1}, '#{3}')]","~=":"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]","|=":"[contains(concat('-', @#{1}, '-'), '-#{3}-')]"},pseudos:{"first-child":"[not(preceding-sibling::*)]","last-child":"[not(following-sibling::*)]",
87
- "only-child":"[not(preceding-sibling::* or following-sibling::*)]",empty:"[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",checked:"[@checked]",disabled:"[@disabled]",enabled:"[not(@disabled)]",not:function(a){for(var b=a[6],c=Selector.patterns,d=Selector.xpath,e,f,g=[];b&&e!=b&&/\S/.test(b);){e=b;for(var h in c)if(a=b.match(c[h])){f=typeof d[h]=="function"?d[h](a):(new Template(d[h])).evaluate(a);g.push("("+f.substring(1,f.length-1)+")");b=b.replace(a[0],"");break}}return"[not("+
88
- g.join(" and ")+")]"},"nth-child":function(a){return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ",a)},"nth-last-child":function(a){return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ",a)},"nth-of-type":function(a){return Selector.xpath.pseudos.nth("position() ",a)},"nth-last-of-type":function(a){return Selector.xpath.pseudos.nth("(last() + 1 - position()) ",a)},"first-of-type":function(a){a[6]="1";return Selector.xpath.pseudos["nth-of-type"](a)},"last-of-type":function(a){a[6]=
89
- "1";return Selector.xpath.pseudos["nth-last-of-type"](a)},"only-of-type":function(a){var b=Selector.xpath.pseudos;return b["first-of-type"](a)+b["last-of-type"](a)},nth:function(a,b){var c,d=b[6];if(d=="even")d="2n+0";if(d=="odd")d="2n+1";if(c=d.match(/^(\d+)$/))return"["+a+"= "+c[1]+"]";if(c=d.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(c[1]=="-")c[1]=-1;d=c[1]?Number(c[1]):1;c=c[2]?Number(c[2]):0;return(new Template("[((#{fragment} - #{b}) mod #{a} = 0) and ((#{fragment} - #{b}) div #{a} >= 0)]")).evaluate({fragment:a,
90
- a:d,b:c})}}}},criteria:{tagName:'n = h.tagName(n, r, "#{1}", c); c = false;',className:'n = h.className(n, r, "#{1}", c); c = false;',id:'n = h.id(n, r, "#{1}", c); c = false;',attrPresence:'n = h.attrPresence(n, r, "#{1}"); c = false;',attr:function(a){a[3]=a[5]||a[6];return(new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;')).evaluate(a)},pseudo:function(a){if(a[6])a[6]=a[6].replace(/"/g,'\\"');return(new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;')).evaluate(a)},
91
- descendant:'c = "descendant";',child:'c = "child";',adjacent:'c = "adjacent";',laterSibling:'c = "laterSibling";'},patterns:{laterSibling:/^\s*~\s*/,child:/^\s*>\s*/,adjacent:/^\s*\+\s*/,descendant:/^\s/,tagName:/^\s*(\*|[\w\-]+)(\b|$)?/,id:/^#([\w\-\*]+)(\b|$)/,className:/^\.([\w\-\*]+)(\b|$)/,pseudo:/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,attrPresence:/^\[([\w]+)\]/,attr:/\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/},
92
- handlers:{concat:function(a,b){for(var c=0,d;d=b[c];c++)a.push(d);return a},mark:function(a){for(var b=0,c;c=a[b];b++)c._counted=true;return a},unmark:function(a){for(var b=0,c;c=a[b];b++)c._counted=undefined;return a},index:function(a,b,c){a._counted=true;if(b){a=a.childNodes;b=a.length-1;for(var d=1;b>=0;b--){node=a[b];if(node.nodeType==1&&(!c||node._counted))node.nodeIndex=d++}}else{b=0;d=1;for(a=a.childNodes;node=a[b];b++)if(node.nodeType==1&&(!c||node._counted))node.nodeIndex=d++}},unique:function(a){if(a.length==
93
- 0)return a;for(var b=[],c,d=0,e=a.length;d<e;d++)if(!(c=a[d])._counted){c._counted=true;b.push(Element.extend(c))}return Selector.handlers.unmark(b)},descendant:function(a){for(var b=Selector.handlers,c=0,d=[],e;e=a[c];c++)b.concat(d,e.getElementsByTagName("*"));return d},child:function(a){for(var b=0,c=[],d;d=a[b];b++)for(var e=0,f;f=d.childNodes[e];e++)f.nodeType==1&&f.tagName!="!"&&c.push(f);return c},adjacent:function(a){for(var b=0,c=[],d;d=a[b];b++)(d=this.nextElementSibling(d))&&c.push(d);
94
- return c},laterSibling:function(a){for(var b=Selector.handlers,c=0,d=[],e;e=a[c];c++)b.concat(d,Element.nextSiblings(e));return d},nextElementSibling:function(a){for(;a=a.nextSibling;)if(a.nodeType==1)return a;return null},previousElementSibling:function(a){for(;a=a.previousSibling;)if(a.nodeType==1)return a;return null},tagName:function(a,b,c,d){c=c.toUpperCase();var e=[],f=Selector.handlers;if(a){if(d){if(d=="descendant"){for(b=0;d=a[b];b++)f.concat(e,d.getElementsByTagName(c));return e}else a=
95
- this[d](a);if(c=="*")return a}for(b=0;d=a[b];b++)d.tagName.toUpperCase()==c&&e.push(d);return e}else return b.getElementsByTagName(c)},id:function(a,b,c,d){c=$(c);var e=Selector.handlers;if(!a&&b==document)return c?[c]:[];if(a){if(d)if(d=="child")for(b=0;d=a[b];b++){if(c.parentNode==d)return[c]}else if(d=="descendant")for(b=0;d=a[b];b++){if(Element.descendantOf(c,d))return[c]}else if(d=="adjacent")for(b=0;d=a[b];b++){if(Selector.handlers.previousElementSibling(c)==d)return[c]}else a=e[d](a);for(b=
96
- 0;d=a[b];b++)if(d==c)return[c];return[]}return c&&Element.descendantOf(c,b)?[c]:[]},className:function(a,b,c,d){if(a&&d)a=this[d](a);return Selector.handlers.byClassName(a,b,c)},byClassName:function(a,b,c){a||(a=Selector.handlers.descendant([b]));b=0;for(var d=[],e,f;e=a[b];b++){f=e.className;if(f.length!=0)if(f==c||(" "+f+" ").include(" "+c+" "))d.push(e)}return d},attrPresence:function(a,b,c){b=[];for(var d=0,e;e=a[d];d++)Element.hasAttribute(e,c)&&b.push(e);return b},attr:function(a,b,c,d,e){a||
97
- (a=b.getElementsByTagName("*"));b=Selector.operators[e];e=[];for(var f=0,g;g=a[f];f++){var h=Element.readAttribute(g,c);h!==null&&b(h,d)&&e.push(g)}return e},pseudo:function(a,b,c,d,e){if(a&&e)a=this[e](a);a||(a=d.getElementsByTagName("*"));return Selector.pseudos[b](a,c,d)}},pseudos:{"first-child":function(a){for(var b=0,c=[],d;d=a[b];b++)Selector.handlers.previousElementSibling(d)||c.push(d);return c},"last-child":function(a){for(var b=0,c=[],d;d=a[b];b++)Selector.handlers.nextElementSibling(d)||
98
- c.push(d);return c},"only-child":function(a){for(var b=Selector.handlers,c=0,d=[],e;e=a[c];c++)!b.previousElementSibling(e)&&!b.nextElementSibling(e)&&d.push(e);return d},"nth-child":function(a,b,c){return Selector.pseudos.nth(a,b,c)},"nth-last-child":function(a,b,c){return Selector.pseudos.nth(a,b,c,true)},"nth-of-type":function(a,b,c){return Selector.pseudos.nth(a,b,c,false,true)},"nth-last-of-type":function(a,b,c){return Selector.pseudos.nth(a,b,c,true,true)},"first-of-type":function(a,b,c){return Selector.pseudos.nth(a,
99
- "1",c,false,true)},"last-of-type":function(a,b,c){return Selector.pseudos.nth(a,"1",c,true,true)},"only-of-type":function(a,b,c){var d=Selector.pseudos;return d["last-of-type"](d["first-of-type"](a,b,c),b,c)},getIndices:function(a,b,c){if(a==0)return b>0?[b]:[];return $R(1,c).inject([],function(d,e){0==(e-b)%a&&(e-b)/a>=0&&d.push(e);return d})},nth:function(a,b,c,d,e){if(a.length==0)return[];if(b=="even")b="2n+0";if(b=="odd")b="2n+1";c=Selector.handlers;var f=[],g=[],h;c.mark(a);for(var i=0;h=a[i];i++)if(!h.parentNode._counted){c.index(h.parentNode,
100
- d,e);g.push(h.parentNode)}if(b.match(/^\d+$/)){b=Number(b);for(i=0;h=a[i];i++)h.nodeIndex==b&&f.push(h)}else if(h=b.match(/^(-?\d*)?n(([+-])(\d+))?/)){if(h[1]=="-")h[1]=-1;i=h[1]?Number(h[1]):1;h=h[2]?Number(h[2]):0;b=Selector.pseudos.getIndices(i,h,a.length);i=0;for(d=b.length;h=a[i];i++)for(e=0;e<d;e++)h.nodeIndex==b[e]&&f.push(h)}c.unmark(a);c.unmark(g);return f},empty:function(a){for(var b=0,c=[],d;d=a[b];b++)d.tagName=="!"||d.firstChild&&!d.innerHTML.match(/^\s*$/)||c.push(d);return c},not:function(a,
101
- b,c){var d=Selector.handlers;b=(new Selector(b)).findElements(c);d.mark(b);c=0;for(var e=[],f;f=a[c];c++)f._counted||e.push(f);d.unmark(b);return e},enabled:function(a){for(var b=0,c=[],d;d=a[b];b++)d.disabled||c.push(d);return c},disabled:function(a){for(var b=0,c=[],d;d=a[b];b++)d.disabled&&c.push(d);return c},checked:function(a){for(var b=0,c=[],d;d=a[b];b++)d.checked&&c.push(d);return c}},operators:{"=":function(a,b){return a==b},"!=":function(a,b){return a!=b},"^=":function(a,b){return a.startsWith(b)},
102
- "$=":function(a,b){return a.endsWith(b)},"*=":function(a,b){return a.include(b)},"~=":function(a,b){return(" "+a+" ").include(" "+b+" ")},"|=":function(a,b){return("-"+a.toUpperCase()+"-").include("-"+b.toUpperCase()+"-")}},matchElements:function(a,b){var c=(new Selector(b)).findElements(),d=Selector.handlers;d.mark(c);for(var e=0,f=[],g;g=a[e];e++)g._counted&&f.push(g);d.unmark(c);return f},findElement:function(a,b,c){if(typeof b=="number"){c=b;b=false}return Selector.matchElements(a,b||"*")[c||
103
- 0]},findChildElements:function(a,b){var c=b.join(",");b=[];c.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/,function(h){b.push(h[1].strip())});c=[];for(var d=Selector.handlers,e=0,f=b.length,g;e<f;e++){g=new Selector(b[e].strip());d.concat(c,g.findElements(a))}return f>1?d.unique(c):c}});function $$(){return Selector.findChildElements(document,$A(arguments))}
104
- var Form={reset:function(a){$(a).reset();return a},serializeElements:function(a,b){var c=a.inject({},function(d,e){if(!e.disabled&&e.name){var f=e.name,g=$(e).getValue();if(g!=null)if(f in d){if(d[f].constructor!=Array)d[f]=[d[f]];d[f].push(g)}else d[f]=g}return d});return b?c:Hash.toQueryString(c)}};
105
- Form.Methods={serialize:function(a,b){return Form.serializeElements(Form.getElements(a),b)},getElements:function(a){return $A($(a).getElementsByTagName("*")).inject([],function(b,c){Form.Element.Serializers[c.tagName.toLowerCase()]&&b.push(Element.extend(c));return b})},getInputs:function(a,b,c){a=$(a);a=a.getElementsByTagName("input");if(!b&&!c)return $A(a).map(Element.extend);for(var d=0,e=[],f=a.length;d<f;d++){var g=a[d];b&&g.type!=b||c&&g.name!=c||e.push(Element.extend(g))}return e},disable:function(a){a=
106
- $(a);Form.getElements(a).invoke("disable");return a},enable:function(a){a=$(a);Form.getElements(a).invoke("enable");return a},findFirstElement:function(a){return $(a).getElements().find(function(b){return b.type!="hidden"&&!b.disabled&&["input","select","textarea"].include(b.tagName.toLowerCase())})},focusFirstElement:function(a){a=$(a);a.findFirstElement().activate();return a},request:function(a,b){a=$(a);b=Object.clone(b||{});var c=b.parameters;b.parameters=a.serialize(true);if(c){if(typeof c==
107
- "string")c=c.toQueryParams();Object.extend(b.parameters,c)}if(a.hasAttribute("method")&&!b.method)b.method=a.method;return new Ajax.Request(a.readAttribute("action"),b)}};Form.Element={focus:function(a){$(a).focus();return a},select:function(a){$(a).select();return a}};
108
- Form.Element.Methods={serialize:function(a){a=$(a);if(!a.disabled&&a.name){var b=a.getValue();if(b!=undefined){var c={};c[a.name]=b;return Hash.toQueryString(c)}}return""},getValue:function(a){a=$(a);var b=a.tagName.toLowerCase();return Form.Element.Serializers[b](a)},clear:function(a){$(a).value="";return a},present:function(a){return $(a).value!=""},activate:function(a){a=$(a);try{a.focus();if(a.select&&(a.tagName.toLowerCase()!="input"||!["button","reset","submit"].include(a.type)))a.select()}catch(b){}return a},
109
- disable:function(a){a=$(a);a.blur();a.disabled=true;return a},enable:function(a){a=$(a);a.disabled=false;return a}};var Field=Form.Element,$F=Form.Element.Methods.getValue;
110
- Form.Element.Serializers={input:function(a){switch(a.type.toLowerCase()){case "checkbox":case "radio":return Form.Element.Serializers.inputSelector(a);default:return Form.Element.Serializers.textarea(a)}},inputSelector:function(a){return a.checked?a.value:null},textarea:function(a){return a.value},select:function(a){return this[a.type=="select-one"?"selectOne":"selectMany"](a)},selectOne:function(a){var b=a.selectedIndex;return b>=0?this.optionValue(a.options[b]):null},selectMany:function(a){var b,
111
- c=a.length;if(!c)return null;var d=0;for(b=[];d<c;d++){var e=a.options[d];e.selected&&b.push(this.optionValue(e))}return b},optionValue:function(a){return Element.extend(a).hasAttribute("value")?a.value:a.text}};Abstract.TimedObserver=function(){};
112
- Abstract.TimedObserver.prototype={initialize:function(a,b,c){this.frequency=b;this.element=$(a);this.callback=c;this.lastValue=this.getValue();this.registerCallback()},registerCallback:function(){setInterval(this.onTimerEvent.bind(this),this.frequency*1E3)},onTimerEvent:function(){var a=this.getValue();if("string"==typeof this.lastValue&&"string"==typeof a?this.lastValue!=a:String(this.lastValue)!=String(a)){this.callback(this.element,a);this.lastValue=a}}};Form.Element.Observer=Class.create();
113
- Form.Element.Observer.prototype=Object.extend(new Abstract.TimedObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.Observer=Class.create();Form.Observer.prototype=Object.extend(new Abstract.TimedObserver,{getValue:function(){return Form.serialize(this.element)}});Abstract.EventObserver=function(){};
114
- Abstract.EventObserver.prototype={initialize:function(a,b){this.element=$(a);this.callback=b;this.lastValue=this.getValue();this.element.tagName.toLowerCase()=="form"?this.registerFormCallbacks():this.registerCallback(this.element)},onElementEvent:function(){var a=this.getValue();if(this.lastValue!=a){this.callback(this.element,a);this.lastValue=a}},registerFormCallbacks:function(){Form.getElements(this.element).each(this.registerCallback.bind(this))},registerCallback:function(a){if(a.type)switch(a.type.toLowerCase()){case "checkbox":case "radio":Event.observe(a,
115
- "click",this.onElementEvent.bind(this));break;default:Event.observe(a,"change",this.onElementEvent.bind(this));break}}};Form.Element.EventObserver=Class.create();Form.Element.EventObserver.prototype=Object.extend(new Abstract.EventObserver,{getValue:function(){return Form.Element.getValue(this.element)}});Form.EventObserver=Class.create();Form.EventObserver.prototype=Object.extend(new Abstract.EventObserver,{getValue:function(){return Form.serialize(this.element)}});if(!window.Event)var Event={};
116
- Object.extend(Event,{KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,KEY_HOME:36,KEY_END:35,KEY_PAGEUP:33,KEY_PAGEDOWN:34,element:function(a){return $(a.target||a.srcElement)},isLeftClick:function(a){return a.which&&a.which==1||a.button&&a.button==1},pointerX:function(a){return a.pageX||a.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft)},pointerY:function(a){return a.pageY||a.clientY+(document.documentElement.scrollTop||
117
- document.body.scrollTop)},stop:function(a){if(a.preventDefault){a.preventDefault();a.stopPropagation()}else{a.returnValue=false;a.cancelBubble=true}},findElement:function(a,b){for(var c=Event.element(a);c.parentNode&&(!c.tagName||c.tagName.toUpperCase()!=b.toUpperCase());)c=c.parentNode;return c},observers:false,_observeAndCache:function(a,b,c,d){if(!this.observers)this.observers=[];if(a.addEventListener){this.observers.push([a,b,c,d]);a.addEventListener(b,c,d)}else if(a.attachEvent){this.observers.push([a,
118
- b,c,d]);a.attachEvent("on"+b,c)}},unloadCache:function(){if(Event.observers){for(var a=0,b=Event.observers.length;a<b;a++){Event.stopObserving.apply(this,Event.observers[a]);Event.observers[a][0]=null}Event.observers=false}},observe:function(a,b,c,d){a=$(a);if(b=="keypress"&&(Prototype.Browser.WebKit||a.attachEvent))b="keydown";Event._observeAndCache(a,b,c,d||false)},stopObserving:function(a,b,c,d){a=$(a);d=d||false;if(b=="keypress"&&(Prototype.Browser.WebKit||a.attachEvent))b="keydown";if(a.removeEventListener)a.removeEventListener(b,
119
- c,d);else if(a.detachEvent)try{a.detachEvent("on"+b,c)}catch(e){}}});Prototype.Browser.IE&&Event.observe(window,"unload",Event.unloadCache,false);
120
- var Position={includeScrollOffsets:false,prepare:function(){this.deltaX=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;this.deltaY=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0},realOffset:function(a){var b=0,c=0;do{b+=a.scrollTop||0;c+=a.scrollLeft||0;a=a.parentNode}while(a);return[c,b]},cumulativeOffset:function(a){var b=0,c=0;do{b+=a.offsetTop||0;c+=a.offsetLeft||0;a=a.offsetParent}while(a);return[c,b]},positionedOffset:function(a){var b=
121
- 0,c=0;do{b+=a.offsetTop||0;c+=a.offsetLeft||0;if(a=a.offsetParent){if(a.tagName=="BODY")break;var d=Element.getStyle(a,"position");if(d=="relative"||d=="absolute")break}}while(a);return[c,b]},offsetParent:function(a){if(a.offsetParent)return a.offsetParent;if(a==document.body)return a;for(;(a=a.parentNode)&&a!=document.body;)if(Element.getStyle(a,"position")!="static")return a;return document.body},within:function(a,b,c){if(this.includeScrollOffsets)return this.withinIncludingScrolloffsets(a,b,c);
122
- this.xcomp=b;this.ycomp=c;this.offset=this.cumulativeOffset(a);return c>=this.offset[1]&&c<this.offset[1]+a.offsetHeight&&b>=this.offset[0]&&b<this.offset[0]+a.offsetWidth},withinIncludingScrolloffsets:function(a,b,c){var d=this.realOffset(a);this.xcomp=b+d[0]-this.deltaX;this.ycomp=c+d[1]-this.deltaY;this.offset=this.cumulativeOffset(a);return this.ycomp>=this.offset[1]&&this.ycomp<this.offset[1]+a.offsetHeight&&this.xcomp>=this.offset[0]&&this.xcomp<this.offset[0]+a.offsetWidth},overlap:function(a,
123
- b){if(!a)return 0;if(a=="vertical")return(this.offset[1]+b.offsetHeight-this.ycomp)/b.offsetHeight;if(a=="horizontal")return(this.offset[0]+b.offsetWidth-this.xcomp)/b.offsetWidth},page:function(a){var b=0,c=0,d=a;do{b+=d.offsetTop||0;c+=d.offsetLeft||0;if(d.offsetParent==document.body)if(Element.getStyle(d,"position")=="absolute")break}while(d=d.offsetParent);d=a;do if(!window.opera||d.tagName=="BODY"){b-=d.scrollTop||0;c-=d.scrollLeft||0}while(d=d.parentNode);return[c,b]},clone:function(a,b,c){c=
124
- Object.extend({setLeft:true,setTop:true,setWidth:true,setHeight:true,offsetTop:0,offsetLeft:0},c||{});a=$(a);var d=Position.page(a);b=$(b);var e=[0,0],f=null;if(Element.getStyle(b,"position")=="absolute"){f=Position.offsetParent(b);e=Position.page(f)}if(f==document.body){e[0]-=document.body.offsetLeft;e[1]-=document.body.offsetTop}if(c.setLeft)b.style.left=d[0]-e[0]+c.offsetLeft+"px";if(c.setTop)b.style.top=d[1]-e[1]+c.offsetTop+"px";if(c.setWidth)b.style.width=a.offsetWidth+"px";if(c.setHeight)b.style.height=
125
- a.offsetHeight+"px"},absolutize:function(a){a=$(a);if(a.style.position!="absolute"){Position.prepare();var b=Position.positionedOffset(a),c=b[1];b=b[0];var d=a.clientWidth,e=a.clientHeight;a._originalLeft=b-parseFloat(a.style.left||0);a._originalTop=c-parseFloat(a.style.top||0);a._originalWidth=a.style.width;a._originalHeight=a.style.height;a.style.position="absolute";a.style.top=c+"px";a.style.left=b+"px";a.style.width=d+"px";a.style.height=e+"px"}},relativize:function(a){a=$(a);if(a.style.position!=
126
- "relative"){Position.prepare();a.style.position="relative";var b=parseFloat(a.style.top||0)-(a._originalTop||0),c=parseFloat(a.style.left||0)-(a._originalLeft||0);a.style.top=b+"px";a.style.left=c+"px";a.style.height=a._originalHeight;a.style.width=a._originalWidth}}};
127
- if(Prototype.Browser.WebKit)Position.cumulativeOffset=function(a){var b=0,c=0;do{b+=a.offsetTop||0;c+=a.offsetLeft||0;if(a.offsetParent==document.body)if(Element.getStyle(a,"position")=="absolute")break;a=a.offsetParent}while(a);return[c,b]};Element.addMethods();var Scriptaculous={Version:"1.7.1_beta3",require:function(a){document.write('<script type="text/javascript" src="'+a+'"><\/script>')},REQUIRED_PROTOTYPE:"1.5.1",load:function(){function a(b){b=b.split(".");return parseInt(b[0])*1E5+parseInt(b[1])*1E3+parseInt(b[2])}if(typeof Prototype=="undefined"||typeof Element=="undefined"||typeof Element.Methods=="undefined"||a(Prototype.Version)<a(Scriptaculous.REQUIRED_PROTOTYPE))throw"script.aculo.us requires the Prototype JavaScript framework >= "+Scriptaculous.REQUIRED_PROTOTYPE;
128
- $A(document.getElementsByTagName("script")).findAll(function(b){return b.src&&b.src.match(/scriptaculous\.js(\?.*)?$/)}).each(function(b){var c=b.src.replace(/scriptaculous\.js(\?.*)?$/,"");b=b.src.match(/\?.*load=([a-z,]*)/);(b?b[1]:"builder,effects,dragdrop,controls,slider,sound").split(",").each(function(d){Scriptaculous.require(c+d+".js")})})}};Scriptaculous.load();String.prototype.parseColor=function(a){var b="#";if(this.slice(0,4)=="rgb("){var c=this.slice(4,this.length-1).split(","),d=0;do b+=parseInt(c[d]).toColorPart();while(++d<3)}else if(this.slice(0,1)=="#"){if(this.length==4)for(d=1;d<4;d++)b+=(this.charAt(d)+this.charAt(d)).toLowerCase();if(this.length==7)b=this.toLowerCase()}return b.length==7?b:a||this};
129
- Element.collectTextNodes=function(a){return $A($(a).childNodes).collect(function(b){return b.nodeType==3?b.nodeValue:b.hasChildNodes()?Element.collectTextNodes(b):""}).flatten().join("")};Element.collectTextNodesIgnoreClass=function(a,b){return $A($(a).childNodes).collect(function(c){return c.nodeType==3?c.nodeValue:c.hasChildNodes()&&!Element.hasClassName(c,b)?Element.collectTextNodesIgnoreClass(c,b):""}).flatten().join("")};
130
- Element.setContentZoom=function(a,b){a=$(a);a.setStyle({fontSize:b/100+"em"});Prototype.Browser.WebKit&&window.scrollBy(0,0);return a};Element.getInlineOpacity=function(a){return $(a).style.opacity||""};Element.forceRerendering=function(a){try{a=$(a);var b=document.createTextNode(" ");a.appendChild(b);a.removeChild(b)}catch(c){}};Array.prototype.call=function(){var a=arguments;this.each(function(b){b.apply(this,a)})};
131
- var Effect={_elementDoesNotExistError:{name:"ElementDoesNotExistError",message:"The specified DOM element does not exist, but is required for this effect to operate"},tagifyText:function(a){if(typeof Builder=="undefined")throw"Effect.tagifyText requires including script.aculo.us' builder.js library";var b="position:relative";if(Prototype.Browser.IE)b+=";zoom:1";a=$(a);$A(a.childNodes).each(function(c){if(c.nodeType==3){c.nodeValue.toArray().each(function(d){a.insertBefore(Builder.node("span",{style:b},
132
- d==" "?String.fromCharCode(160):d),c)});Element.remove(c)}})},multiple:function(a,b,c){a=(typeof a=="object"||typeof a=="function")&&a.length?a:$(a).childNodes;var d=Object.extend({speed:0.1,delay:0},c||{}),e=d.delay;$A(a).each(function(f,g){new b(f,Object.extend(d,{delay:g*d.speed+e}))})},PAIRS:{slide:["SlideDown","SlideUp"],blind:["BlindDown","BlindUp"],appear:["Appear","Fade"]},toggle:function(a,b,c){a=$(a);b=(b||"appear").toLowerCase();c=Object.extend({queue:{position:"end",scope:a.id||"global",
133
- limit:1}},c||{});Effect[a.visible()?Effect.PAIRS[b][1]:Effect.PAIRS[b][0]](a,c)}},Effect2=Effect;Effect.Transitions={linear:Prototype.K,sinoidal:function(a){return-Math.cos(a*Math.PI)/2+0.5},reverse:function(a){return 1-a},flicker:function(a){a=-Math.cos(a*Math.PI)/4+0.75+Math.random()/4;return a>1?1:a},wobble:function(a){return-Math.cos(a*Math.PI*9*a)/2+0.5},pulse:function(a,b){b=b||5;return Math.round(a%(1/b)*b)==0?a*b*2-Math.floor(a*b*2):1-(a*b*2-Math.floor(a*b*2))},none:function(){return 0},full:function(){return 1}};
134
- Effect.ScopedQueue=Class.create();
135
- Object.extend(Object.extend(Effect.ScopedQueue.prototype,Enumerable),{initialize:function(){this.effects=[];this.interval=null},_each:function(a){this.effects._each(a)},add:function(a){var b=(new Date).getTime();switch(typeof a.options.queue=="string"?a.options.queue:a.options.queue.position){case "front":this.effects.findAll(function(c){return c.state=="idle"}).each(function(c){c.startOn+=a.finishOn;c.finishOn+=a.finishOn});break;case "with-last":b=this.effects.pluck("startOn").max()||b;break;case "end":b=
136
- this.effects.pluck("finishOn").max()||b;break}a.startOn+=b;a.finishOn+=b;if(!a.options.queue.limit||this.effects.length<a.options.queue.limit)this.effects.push(a);if(!this.interval)this.interval=setInterval(this.loop.bind(this),15)},remove:function(a){this.effects=this.effects.reject(function(b){return b==a});if(this.effects.length==0){clearInterval(this.interval);this.interval=null}},loop:function(){for(var a=(new Date).getTime(),b=0,c=this.effects.length;b<c;b++)this.effects[b]&&this.effects[b].loop(a)}});
137
- Effect.Queues={instances:$H(),get:function(a){if(typeof a!="string")return a;this.instances[a]||(this.instances[a]=new Effect.ScopedQueue);return this.instances[a]}};Effect.Queue=Effect.Queues.get("global");Effect.DefaultOptions={transition:Effect.Transitions.sinoidal,duration:1,fps:100,sync:false,from:0,to:1,delay:0,queue:"parallel"};Effect.Base=function(){};
138
- Effect.Base.prototype={position:null,start:function(a){function b(c,d){return(c[d+"Internal"]?"this.options."+d+"Internal(this);":"")+(c[d]?"this.options."+d+"(this);":"")}if(a.transition===false)a.transition=Effect.Transitions.linear;this.options=Object.extend(Object.extend({},Effect.DefaultOptions),a||{});this.currentFrame=0;this.state="idle";this.startOn=this.options.delay*1E3;this.finishOn=this.startOn+this.options.duration*1E3;this.fromToDelta=this.options.to-this.options.from;this.totalTime=
139
- this.finishOn-this.startOn;this.totalFrames=this.options.fps*this.options.duration;eval('this.render = function(pos){ if(this.state=="idle"){this.state="running";'+b(a,"beforeSetup")+(this.setup?"this.setup();":"")+b(a,"afterSetup")+'};if(this.state=="running"){pos=this.options.transition(pos)*'+this.fromToDelta+"+"+this.options.from+";this.position=pos;"+b(a,"beforeUpdate")+(this.update?"this.update(pos);":"")+b(a,"afterUpdate")+"}}");this.event("beforeStart");this.options.sync||Effect.Queues.get(typeof this.options.queue==
140
- "string"?"global":this.options.queue.scope).add(this)},loop:function(a){if(a>=this.startOn)if(a>=this.finishOn){this.render(1);this.cancel();this.event("beforeFinish");this.finish&&this.finish();this.event("afterFinish")}else{a=(a-this.startOn)/this.totalTime;var b=Math.round(a*this.totalFrames);if(b>this.currentFrame){this.render(a);this.currentFrame=b}}},cancel:function(){this.options.sync||Effect.Queues.get(typeof this.options.queue=="string"?"global":this.options.queue.scope).remove(this);this.state=
141
- "finished"},event:function(a){this.options[a+"Internal"]&&this.options[a+"Internal"](this);this.options[a]&&this.options[a](this)},inspect:function(){var a=$H();for(property in this)if(typeof this[property]!="function")a[property]=this[property];return"#<Effect:"+a.inspect()+",options:"+$H(this.options).inspect()+">"}};Effect.Parallel=Class.create();
142
- Object.extend(Object.extend(Effect.Parallel.prototype,Effect.Base.prototype),{initialize:function(a,b){this.effects=a||[];this.start(b)},update:function(a){this.effects.invoke("render",a)},finish:function(a){this.effects.each(function(b){b.render(1);b.cancel();b.event("beforeFinish");b.finish&&b.finish(a);b.event("afterFinish")})}});Effect.Event=Class.create();
143
- Object.extend(Object.extend(Effect.Event.prototype,Effect.Base.prototype),{initialize:function(a){this.start(Object.extend({duration:0},a||{}))},update:Prototype.emptyFunction});Effect.Opacity=Class.create();
144
- Object.extend(Object.extend(Effect.Opacity.prototype,Effect.Base.prototype),{initialize:function(a,b){this.element=$(a);if(!this.element)throw Effect._elementDoesNotExistError;Prototype.Browser.IE&&!this.element.currentStyle.hasLayout&&this.element.setStyle({zoom:1});this.start(Object.extend({from:this.element.getOpacity()||0,to:1},b||{}))},update:function(a){this.element.setOpacity(a)}});Effect.Move=Class.create();
145
- Object.extend(Object.extend(Effect.Move.prototype,Effect.Base.prototype),{initialize:function(a,b){this.element=$(a);if(!this.element)throw Effect._elementDoesNotExistError;this.start(Object.extend({x:0,y:0,mode:"relative"},b||{}))},setup:function(){this.element.makePositioned();this.originalLeft=parseFloat(this.element.getStyle("left")||"0");this.originalTop=parseFloat(this.element.getStyle("top")||"0");if(this.options.mode=="absolute"){this.options.x-=this.originalLeft;this.options.y-=this.originalTop}},
146
- update:function(a){this.element.setStyle({left:Math.round(this.options.x*a+this.originalLeft)+"px",top:Math.round(this.options.y*a+this.originalTop)+"px"})}});Effect.MoveBy=function(a,b,c,d){return new Effect.Move(a,Object.extend({x:c,y:b},d||{}))};Effect.Scale=Class.create();
147
- Object.extend(Object.extend(Effect.Scale.prototype,Effect.Base.prototype),{initialize:function(a,b,c){this.element=$(a);if(!this.element)throw Effect._elementDoesNotExistError;this.start(Object.extend({scaleX:true,scaleY:true,scaleContent:true,scaleFromCenter:false,scaleMode:"box",scaleFrom:100,scaleTo:b},c||{}))},setup:function(){this.restoreAfterFinish=this.options.restoreAfterFinish||false;this.elementPositioning=this.element.getStyle("position");this.originalStyle={};["top","left","width","height",
148
- "fontSize"].each(function(b){this.originalStyle[b]=this.element.style[b]}.bind(this));this.originalTop=this.element.offsetTop;this.originalLeft=this.element.offsetLeft;var a=this.element.getStyle("font-size")||"100%";["em","px","%","pt"].each(function(b){if(a.indexOf(b)>0){this.fontSize=parseFloat(a);this.fontSizeType=b}}.bind(this));this.factor=(this.options.scaleTo-this.options.scaleFrom)/100;this.dims=null;if(this.options.scaleMode=="box")this.dims=[this.element.offsetHeight,this.element.offsetWidth];
149
- if(/^content/.test(this.options.scaleMode))this.dims=[this.element.scrollHeight,this.element.scrollWidth];if(!this.dims)this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth]},update:function(a){a=this.options.scaleFrom/100+this.factor*a;this.options.scaleContent&&this.fontSize&&this.element.setStyle({fontSize:this.fontSize*a+this.fontSizeType});this.setDimensions(this.dims[0]*a,this.dims[1]*a)},finish:function(){this.restoreAfterFinish&&this.element.setStyle(this.originalStyle)},
150
- setDimensions:function(a,b){var c={};if(this.options.scaleX)c.width=Math.round(b)+"px";if(this.options.scaleY)c.height=Math.round(a)+"px";if(this.options.scaleFromCenter){var d=(a-this.dims[0])/2,e=(b-this.dims[1])/2;if(this.elementPositioning=="absolute"){if(this.options.scaleY)c.top=this.originalTop-d+"px";if(this.options.scaleX)c.left=this.originalLeft-e+"px"}else{if(this.options.scaleY)c.top=-d+"px";if(this.options.scaleX)c.left=-e+"px"}}this.element.setStyle(c)}});Effect.Highlight=Class.create();
151
- Object.extend(Object.extend(Effect.Highlight.prototype,Effect.Base.prototype),{initialize:function(a,b){this.element=$(a);if(!this.element)throw Effect._elementDoesNotExistError;this.start(Object.extend({startcolor:"#ffff99"},b||{}))},setup:function(){if(this.element.getStyle("display")=="none")this.cancel();else{this.oldStyle={};if(!this.options.keepBackgroundImage){this.oldStyle.backgroundImage=this.element.getStyle("background-image");this.element.setStyle({backgroundImage:"none"})}if(!this.options.endcolor)this.options.endcolor=
152
- this.element.getStyle("background-color").parseColor("#ffffff");if(!this.options.restorecolor)this.options.restorecolor=this.element.getStyle("background-color");this._base=$R(0,2).map(function(a){return parseInt(this.options.startcolor.slice(a*2+1,a*2+3),16)}.bind(this));this._delta=$R(0,2).map(function(a){return parseInt(this.options.endcolor.slice(a*2+1,a*2+3),16)-this._base[a]}.bind(this))}},update:function(a){this.element.setStyle({backgroundColor:$R(0,2).inject("#",function(b,c,d){return b+
153
- Math.round(this._base[d]+this._delta[d]*a).toColorPart()}.bind(this))})},finish:function(){this.element.setStyle(Object.extend(this.oldStyle,{backgroundColor:this.options.restorecolor}))}});Effect.ScrollTo=Class.create();
154
- Object.extend(Object.extend(Effect.ScrollTo.prototype,Effect.Base.prototype),{initialize:function(a,b){this.element=$(a);this.start(b||{})},setup:function(){Position.prepare();var a=Position.cumulativeOffset(this.element);if(this.options.offset)a[1]+=this.options.offset;var b=window.innerHeight?window.height-window.innerHeight:document.body.scrollHeight-(document.documentElement.clientHeight?document.documentElement.clientHeight:document.body.clientHeight);this.scrollStart=Position.deltaY;this.delta=
155
- (a[1]>b?b:a[1])-this.scrollStart},update:function(a){Position.prepare();window.scrollTo(Position.deltaX,this.scrollStart+a*this.delta)}});Effect.Fade=function(a,b){a=$(a);var c=a.getInlineOpacity(),d=Object.extend({from:a.getOpacity()||1,to:0,afterFinishInternal:function(e){e.options.to==0&&e.element.hide().setStyle({opacity:c})}},b||{});return new Effect.Opacity(a,d)};
156
- Effect.Appear=function(a,b){a=$(a);var c=Object.extend({from:a.getStyle("display")=="none"?0:a.getOpacity()||0,to:1,afterFinishInternal:function(d){d.element.forceRerendering()},beforeSetup:function(d){d.element.setOpacity(d.options.from).show()}},b||{});return new Effect.Opacity(a,c)};
157
- Effect.Puff=function(a,b){a=$(a);var c={opacity:a.getInlineOpacity(),position:a.getStyle("position"),top:a.style.top,left:a.style.left,width:a.style.width,height:a.style.height};return new Effect.Parallel([new Effect.Scale(a,200,{sync:true,scaleFromCenter:true,scaleContent:true,restoreAfterFinish:true}),new Effect.Opacity(a,{sync:true,to:0})],Object.extend({duration:1,beforeSetupInternal:function(d){Position.absolutize(d.effects[0].element)},afterFinishInternal:function(d){d.effects[0].element.hide().setStyle(c)}},
158
- b||{}))};Effect.BlindUp=function(a,b){a=$(a);a.makeClipping();return new Effect.Scale(a,0,Object.extend({scaleContent:false,scaleX:false,restoreAfterFinish:true,afterFinishInternal:function(c){c.element.hide().undoClipping()}},b||{}))};
159
- Effect.BlindDown=function(a,b){a=$(a);var c=a.getDimensions();return new Effect.Scale(a,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:c.height,originalWidth:c.width},restoreAfterFinish:true,afterSetup:function(d){d.element.makeClipping().setStyle({height:"0px"}).show()},afterFinishInternal:function(d){d.element.undoClipping()}},b||{}))};
160
- Effect.SwitchOff=function(a,b){a=$(a);var c=a.getInlineOpacity();return new Effect.Appear(a,Object.extend({duration:0.4,from:0,transition:Effect.Transitions.flicker,afterFinishInternal:function(d){new Effect.Scale(d.element,1,{duration:0.3,scaleFromCenter:true,scaleX:false,scaleContent:false,restoreAfterFinish:true,beforeSetup:function(e){e.element.makePositioned().makeClipping()},afterFinishInternal:function(e){e.element.hide().undoClipping().undoPositioned().setStyle({opacity:c})}})}},b||{}))};
161
- Effect.DropOut=function(a,b){a=$(a);var c={top:a.getStyle("top"),left:a.getStyle("left"),opacity:a.getInlineOpacity()};return new Effect.Parallel([new Effect.Move(a,{x:0,y:100,sync:true}),new Effect.Opacity(a,{sync:true,to:0})],Object.extend({duration:0.5,beforeSetup:function(d){d.effects[0].element.makePositioned()},afterFinishInternal:function(d){d.effects[0].element.hide().undoPositioned().setStyle(c)}},b||{}))};
162
- Effect.Shake=function(a){a=$(a);var b={top:a.getStyle("top"),left:a.getStyle("left")};return new Effect.Move(a,{x:20,y:0,duration:0.05,afterFinishInternal:function(c){new Effect.Move(c.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(d){new Effect.Move(d.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(e){new Effect.Move(e.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(f){new Effect.Move(f.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(g){new Effect.Move(g.element,
163
- {x:-20,y:0,duration:0.05,afterFinishInternal:function(h){h.element.undoPositioned().setStyle(b)}})}})}})}})}})}})};
164
- Effect.SlideDown=function(a,b){a=$(a).cleanWhitespace();var c=a.down().getStyle("bottom"),d=a.getDimensions();return new Effect.Scale(a,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:window.opera?0:1,scaleMode:{originalHeight:d.height,originalWidth:d.width},restoreAfterFinish:true,afterSetup:function(e){e.element.makePositioned();e.element.down().makePositioned();window.opera&&e.element.setStyle({top:""});e.element.makeClipping().setStyle({height:"0px"}).show()},afterUpdateInternal:function(e){e.element.down().setStyle({bottom:e.dims[0]-
165
- e.element.clientHeight+"px"})},afterFinishInternal:function(e){e.element.undoClipping().undoPositioned();e.element.down().undoPositioned().setStyle({bottom:c})}},b||{}))};
166
- Effect.SlideUp=function(a,b){a=$(a).cleanWhitespace();var c=a.down().getStyle("bottom");return new Effect.Scale(a,window.opera?0:1,Object.extend({scaleContent:false,scaleX:false,scaleMode:"box",scaleFrom:100,restoreAfterFinish:true,beforeStartInternal:function(d){d.element.makePositioned();d.element.down().makePositioned();window.opera&&d.element.setStyle({top:""});d.element.makeClipping().show()},afterUpdateInternal:function(d){d.element.down().setStyle({bottom:d.dims[0]-d.element.clientHeight+"px"})},
167
- afterFinishInternal:function(d){d.element.hide().undoClipping().undoPositioned().setStyle({bottom:c});d.element.down().undoPositioned()}},b||{}))};Effect.Squish=function(a){return new Effect.Scale(a,window.opera?1:0,{restoreAfterFinish:true,beforeSetup:function(b){b.element.makeClipping()},afterFinishInternal:function(b){b.element.hide().undoClipping()}})};
168
- Effect.Grow=function(a,b){a=$(a);var c=Object.extend({direction:"center",moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.full},b||{}),d={top:a.style.top,left:a.style.left,height:a.style.height,width:a.style.width,opacity:a.getInlineOpacity()},e=a.getDimensions(),f,g,h,i;switch(c.direction){case "top-left":f=g=h=i=0;break;case "top-right":f=e.width;g=i=0;h=-e.width;break;case "bottom-left":f=h=0;g=e.height;i=-e.height;break;
169
- case "bottom-right":f=e.width;g=e.height;h=-e.width;i=-e.height;break;case "center":f=e.width/2;g=e.height/2;h=-e.width/2;i=-e.height/2;break}return new Effect.Move(a,{x:f,y:g,duration:0.01,beforeSetup:function(j){j.element.hide().makeClipping().makePositioned()},afterFinishInternal:function(j){new Effect.Parallel([new Effect.Opacity(j.element,{sync:true,to:1,from:0,transition:c.opacityTransition}),new Effect.Move(j.element,{x:h,y:i,sync:true,transition:c.moveTransition}),new Effect.Scale(j.element,
170
- 100,{scaleMode:{originalHeight:e.height,originalWidth:e.width},sync:true,scaleFrom:window.opera?1:0,transition:c.scaleTransition,restoreAfterFinish:true})],Object.extend({beforeSetup:function(k){k.effects[0].element.setStyle({height:"0px"}).show()},afterFinishInternal:function(k){k.effects[0].element.undoClipping().undoPositioned().setStyle(d)}},c))}})};
171
- Effect.Shrink=function(a,b){a=$(a);var c=Object.extend({direction:"center",moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.none},b||{}),d={top:a.style.top,left:a.style.left,height:a.style.height,width:a.style.width,opacity:a.getInlineOpacity()},e=a.getDimensions(),f,g;switch(c.direction){case "top-left":f=g=0;break;case "top-right":f=e.width;g=0;break;case "bottom-left":f=0;g=e.height;break;case "bottom-right":f=e.width;g=
172
- e.height;break;case "center":f=e.width/2;g=e.height/2;break}return new Effect.Parallel([new Effect.Opacity(a,{sync:true,to:0,from:1,transition:c.opacityTransition}),new Effect.Scale(a,window.opera?1:0,{sync:true,transition:c.scaleTransition,restoreAfterFinish:true}),new Effect.Move(a,{x:f,y:g,sync:true,transition:c.moveTransition})],Object.extend({beforeStartInternal:function(h){h.effects[0].element.makePositioned().makeClipping()},afterFinishInternal:function(h){h.effects[0].element.hide().undoClipping().undoPositioned().setStyle(d)}},
173
- c))};Effect.Pulsate=function(a,b){a=$(a);var c=b||{},d=a.getInlineOpacity(),e=c.transition||Effect.Transitions.sinoidal,f=function(g){return e(1-Effect.Transitions.pulse(g,c.pulses))};f.bind(e);return new Effect.Opacity(a,Object.extend(Object.extend({duration:2,from:0,afterFinishInternal:function(g){g.element.setStyle({opacity:d})}},c),{transition:f}))};
174
- Effect.Fold=function(a,b){a=$(a);var c={top:a.style.top,left:a.style.left,width:a.style.width,height:a.style.height};a.makeClipping();return new Effect.Scale(a,5,Object.extend({scaleContent:false,scaleX:false,afterFinishInternal:function(){new Effect.Scale(a,1,{scaleContent:false,scaleY:false,afterFinishInternal:function(d){d.element.hide().undoClipping().setStyle(c)}})}},b||{}))};Effect.Morph=Class.create();
175
- Object.extend(Object.extend(Effect.Morph.prototype,Effect.Base.prototype),{initialize:function(a,b){this.element=$(a);if(!this.element)throw Effect._elementDoesNotExistError;var c=Object.extend({style:{}},b||{});if(typeof c.style=="string")if(c.style.indexOf(":")==-1){var d="",e="."+c.style;$A(document.styleSheets).reverse().each(function(f){if(f.cssRules)cssRules=f.cssRules;else if(f.rules)cssRules=f.rules;$A(cssRules).reverse().each(function(g){if(e==g.selectorText){d=g.style.cssText;throw $break;
176
- }});if(d)throw $break;});this.style=d.parseStyle();c.afterFinishInternal=function(f){f.element.addClassName(f.options.style);f.transforms.each(function(g){if(g.style!="opacity")f.element.style[g.style]=""})}}else this.style=c.style.parseStyle();else this.style=$H(c.style);this.start(c)},setup:function(){function a(b){if(!b||["rgba(0, 0, 0, 0)","transparent"].include(b))b="#ffffff";b=b.parseColor();return $R(0,2).map(function(c){return parseInt(b.slice(c*2+1,c*2+3),16)})}this.transforms=this.style.map(function(b){var c=
177
- b[0];b=b[1];var d=null;if(b.parseColor("#zzzzzz")!="#zzzzzz"){b=b.parseColor();d="color"}else if(c=="opacity"){b=parseFloat(b);Prototype.Browser.IE&&!this.element.currentStyle.hasLayout&&this.element.setStyle({zoom:1})}else if(Element.CSS_LENGTH.test(b)){d=b.match(/^([\+\-]?[0-9\.]+)(.*)$/);b=parseFloat(d[1]);d=d.length==3?d[2]:null}var e=this.element.getStyle(c);return{style:c.camelize(),originalValue:d=="color"?a(e):parseFloat(e||0),targetValue:d=="color"?a(b):b,unit:d}}.bind(this)).reject(function(b){return b.originalValue==
178
- b.targetValue||b.unit!="color"&&(isNaN(b.originalValue)||isNaN(b.targetValue))})},update:function(a){for(var b={},c,d=this.transforms.length;d--;)b[(c=this.transforms[d]).style]=c.unit=="color"?"#"+Math.round(c.originalValue[0]+(c.targetValue[0]-c.originalValue[0])*a).toColorPart()+Math.round(c.originalValue[1]+(c.targetValue[1]-c.originalValue[1])*a).toColorPart()+Math.round(c.originalValue[2]+(c.targetValue[2]-c.originalValue[2])*a).toColorPart():c.originalValue+Math.round((c.targetValue-c.originalValue)*
179
- a*1E3)/1E3+c.unit;this.element.setStyle(b,true)}});Effect.Transform=Class.create();
180
- Object.extend(Effect.Transform.prototype,{initialize:function(a,b){this.tracks=[];this.options=b||{};this.addTracks(a)},addTracks:function(a){a.each(function(b){var c=$H(b).values().first();this.tracks.push($H({ids:$H(b).keys().first(),effect:Effect.Morph,options:{style:c}}))}.bind(this));return this},play:function(){return new Effect.Parallel(this.tracks.map(function(a){return[$(a.ids)||$$(a.ids)].flatten().map(function(b){return new a.effect(b,Object.extend({sync:true},a.options))})}).flatten(),
181
- this.options)}});Element.CSS_PROPERTIES=$w("backgroundColor backgroundPosition borderBottomColor borderBottomStyle borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth borderRightColor borderRightStyle borderRightWidth borderSpacing borderTopColor borderTopStyle borderTopWidth bottom clip color fontSize fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop markerOffset maxHeight maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft paddingRight paddingTop right textIndent top width wordSpacing zIndex");
182
- Element.CSS_LENGTH=/^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;String.prototype.parseStyle=function(){var a=document.createElement("div");a.innerHTML='<div style="'+this+'"></div>';var b=a.childNodes[0].style,c=$H();Element.CSS_PROPERTIES.each(function(d){if(b[d])c[d]=b[d]});if(Prototype.Browser.IE&&this.indexOf("opacity")>-1)c.opacity=this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];return c};Element.morph=function(a,b,c){new Effect.Morph(a,Object.extend({style:b},c||{}));return a};
183
- ["getInlineOpacity","forceRerendering","setContentZoom","collectTextNodes","collectTextNodesIgnoreClass","morph"].each(function(a){Element.Methods[a]=Element[a]});Element.Methods.visualEffect=function(a,b,c){s=b.dasherize().camelize();effect_class=s.charAt(0).toUpperCase()+s.substring(1);new Effect[effect_class](a,c);return $(a)};Element.addMethods();var Lightbox={activeImage:null,badObjects:["select","object","embed"],container:null,enableSlideshow:null,groupName:null,imageArray:[],options:null,overlayDuration:null,overlayOpacity:null,playSlides:null,refTags:["a","area"],relAttribute:null,resizeDuration:null,slideShowTimer:null,startImage:null,initialize:function(a){if(document.getElementsByTagName){this.options=$H({animate:true,autoPlay:true,borderSize:10,containerID:document,enableSlideshow:true,googleAnalytics:false,imageDataLocation:"south",
184
- initImage:"",loop:true,overlayDuration:0.2,overlayOpacity:0.8,prefix:"",relAttribute:"lightbox",resizeSpeed:7,showGroupName:false,slideTime:4,strings:{closeLink:"close",loadingMsg:"loading",nextLink:"next &raquo;",prevLink:"&laquo; prev",startSlideshow:"start slideshow",stopSlideshow:"stop slideshow",numDisplayPrefix:"Image",numDisplaySeparator:"of"}}).merge(a);if(this.options.animate){this.overlayDuration=Math.max(this.options.overlayDuration,0);this.options.resizeSpeed=Math.max(Math.min(this.options.resizeSpeed,
185
- 10),1);this.resizeDuration=(11-this.options.resizeSpeed)*0.15}else this.resizeDuration=this.overlayDuration=0;this.enableSlideshow=this.options.enableSlideshow;this.overlayOpacity=Math.max(Math.min(this.options.overlayOpacity,1),0);this.playSlides=this.options.autoPlay;this.container=$(this.options.containerID);this.relAttribute=this.options.relAttribute;this.updateImageList();var b=this.container!=document?this.container:document.getElementsByTagName("body").item(0);a=document.createElement("div");
186
- a.setAttribute("id",this.getID("overlay"));a.style.display="none";b.appendChild(a);Event.observe(a,"click",this.end.bindAsEventListener(this));a=document.createElement("div");a.setAttribute("id",this.getID("lightbox"));a.style.display="none";b.appendChild(a);b=document.createElement("div");b.setAttribute("id",this.getID("imageDataContainer"));b.className=this.getID("clearfix");var c=document.createElement("div");c.setAttribute("id",this.getID("imageData"));b.appendChild(c);var d=document.createElement("div");
187
- d.setAttribute("id",this.getID("imageDetails"));c.appendChild(d);var e=document.createElement("span");e.setAttribute("id",this.getID("caption"));d.appendChild(e);e=document.createElement("span");e.setAttribute("id",this.getID("numberDisplay"));d.appendChild(e);e=document.createElement("span");e.setAttribute("id",this.getID("detailsNav"));d.appendChild(e);d=document.createElement("a");d.setAttribute("id",this.getID("prevLinkDetails"));d.setAttribute("href","javascript:void(0);");d.innerHTML=this.options.strings.prevLink;
188
- e.appendChild(d);Event.observe(d,"click",this.showPrev.bindAsEventListener(this));d=document.createElement("a");d.setAttribute("id",this.getID("nextLinkDetails"));d.setAttribute("href","javascript:void(0);");d.innerHTML=this.options.strings.nextLink;e.appendChild(d);Event.observe(d,"click",this.showNext.bindAsEventListener(this));d=document.createElement("a");d.setAttribute("id",this.getID("slideShowControl"));d.setAttribute("href","javascript:void(0);");e.appendChild(d);Event.observe(d,"click",this.toggleSlideShow.bindAsEventListener(this));
189
- e=document.createElement("div");e.setAttribute("id",this.getID("close"));c.appendChild(e);c=document.createElement("a");c.setAttribute("id",this.getID("closeLink"));c.setAttribute("href","javascript:void(0);");c.innerHTML=this.options.strings.closeLink;e.appendChild(c);Event.observe(c,"click",this.end.bindAsEventListener(this));this.options.imageDataLocation=="north"&&a.appendChild(b);e=document.createElement("div");e.setAttribute("id",this.getID("outerImageContainer"));a.appendChild(e);c=document.createElement("div");
190
- c.setAttribute("id",this.getID("imageContainer"));e.appendChild(c);e=document.createElement("img");e.setAttribute("id",this.getID("lightboxImage"));c.appendChild(e);e=document.createElement("div");e.setAttribute("id",this.getID("hoverNav"));c.appendChild(e);d=document.createElement("a");d.setAttribute("id",this.getID("prevLinkImg"));d.setAttribute("href","javascript:void(0);");e.appendChild(d);Event.observe(d,"click",this.showPrev.bindAsEventListener(this));d=document.createElement("a");d.setAttribute("id",
191
- this.getID("nextLinkImg"));d.setAttribute("href","javascript:void(0);");e.appendChild(d);Event.observe(d,"click",this.showNext.bindAsEventListener(this));e=document.createElement("div");e.setAttribute("id",this.getID("loading"));c.appendChild(e);c=document.createElement("a");c.setAttribute("id",this.getID("loadingLink"));c.setAttribute("href","javascript:void(0);");c.innerHTML=this.options.strings.loadingMsg;e.appendChild(c);Event.observe(c,"click",this.end.bindAsEventListener(this));this.options.imageDataLocation!=
192
- "north"&&a.appendChild(b);this.options.initImage!=""&&this.start($(this.options.initImage))}},updateImageList:function(){for(var a,b,c,d=0;d<this.refTags.length;d++){b=this.container.getElementsByTagName(this.refTags[d]);for(var e=0;e<b.length;e++){a=b[e];c=String(a.getAttribute("rel"));if(a.getAttribute("href")&&c.toLowerCase().match(this.relAttribute))a.onclick=function(){Lightbox.start(this);return false}}}},getCaption:function(a){var b=a.title||"";if(b==""){var c=$(a).getElementsBySelector("img").first();
193
- if(c)b=c.getAttribute("title")||c.getAttribute("alt");b||(b=a.innerHTML.stripTags()||a.href||"")}return b},start:function(a){this.hideBadObjects();var b=this.getPageSize();$(this.getID("overlay")).setStyle({height:b.pageHeight+"px"});new Effect.Appear(this.getID("overlay"),{duration:this.overlayDuration,from:0,to:this.overlayOpacity});this.imageArray=[];this.groupName=null;var c=a.getAttribute("rel"),d="";if(c==this.relAttribute){d=this.getCaption(a);this.imageArray.push({link:a.getAttribute("href"),
194
- title:d});this.startImage=0}else{for(var e=this.container.getElementsByTagName(a.tagName),f=0;f<e.length;f++){var g=e[f];if(g.getAttribute("href")&&g.getAttribute("rel")==c){d=this.getCaption(g);this.imageArray.push({link:g.getAttribute("href"),title:d});if(g==a)this.startImage=this.imageArray.length-1}}this.groupName=c.substring(this.relAttribute.length+1,c.length-1)}a=this.getPageScroll().y+b.winHeight/15;$(this.getID("lightbox")).setStyle({top:a+"px"}).show();this.changeImage(this.startImage)},
195
- changeImage:function(a){this.activeImage=a;this.disableKeyboardNav();this.pauseSlideShow();$(this.getID("loading")).show();$(this.getID("lightboxImage")).hide();$(this.getID("hoverNav")).hide();$(this.getID("imageDataContainer")).hide();$(this.getID("numberDisplay")).hide();$(this.getID("detailsNav")).hide();var b=new Image;b.onload=function(){$(Lightbox.getID("lightboxImage")).src=b.src;Lightbox.resizeImageContainer(b.width,b.height)};b.src=this.imageArray[this.activeImage].link;this.options.googleAnalytics&&
196
- urchinTracker(this.imageArray[this.activeImage].link)},resizeImageContainer:function(a,b){var c=$(this.getID("outerImageContainer")).getDimensions(),d=(a+this.options.borderSize*2)/c.width*100,e=(b+this.options.borderSize*2)/c.height*100,f=c.width-this.options.borderSize*2-a;c=c.height-this.options.borderSize*2-b;c!=0&&new Effect.Scale(this.getID("outerImageContainer"),e,{scaleX:false,duration:this.resizeDuration,queue:"front"});f!=0&&new Effect.Scale(this.getID("outerImageContainer"),d,{scaleY:false,
197
- delay:this.resizeDuration,duration:this.resizeDuration});if(c==0&&f==0)navigator.appVersion.indexOf("MSIE")!=-1?this.pause(250):this.pause(100);$(this.getID("prevLinkImg")).setStyle({height:b+"px"});$(this.getID("nextLinkImg")).setStyle({height:b+"px"});$(this.getID("imageDataContainer")).setStyle({width:a+this.options.borderSize*2+"px"});this.showImage()},showImage:function(){$(this.getID("loading")).hide();new Effect.Appear(this.getID("lightboxImage"),{duration:0.5,queue:"end",afterFinish:function(){Lightbox.updateDetails()}});
198
- this.preloadNeighborImages()},updateDetails:function(){$(this.getID("caption")).show();$(this.getID("caption")).update(this.imageArray[this.activeImage].title);if(this.imageArray.length>1){var a=this.options.strings.numDisplayPrefix+" "+eval(this.activeImage+1)+" "+this.options.strings.numDisplaySeparator+" "+this.imageArray.length;if(this.options.showGroupName&&this.groupName!="")a+=" "+this.options.strings.numDisplaySeparator+" "+this.groupName;$(this.getID("numberDisplay")).update(a).show();this.enableSlideshow||
199
- $(this.getID("slideShowControl")).hide();$(this.getID("detailsNav")).show()}new Effect.Parallel([new Effect.SlideDown(this.getID("imageDataContainer"),{sync:true}),new Effect.Appear(this.getID("imageDataContainer"),{sync:true})],{duration:0.65,afterFinish:function(){Lightbox.updateNav()}})},updateNav:function(){if(this.imageArray.length>1){$(this.getID("hoverNav")).show();if(this.enableSlideshow)this.playSlides?this.startSlideShow():this.stopSlideShow()}this.enableKeyboardNav()},startSlideShow:function(){this.playSlides=
200
- true;this.slideShowTimer=new PeriodicalExecuter(function(a){Lightbox.showNext();a.stop()},this.options.slideTime);$(this.getID("slideShowControl")).update(this.options.strings.stopSlideshow)},stopSlideShow:function(){this.playSlides=false;this.slideShowTimer&&this.slideShowTimer.stop();$(this.getID("slideShowControl")).update(this.options.strings.startSlideshow)},toggleSlideShow:function(){this.playSlides?this.stopSlideShow():this.startSlideShow()},pauseSlideShow:function(){this.slideShowTimer&&this.slideShowTimer.stop()},
201
- showNext:function(){if(this.imageArray.length>1){if(!this.options.loop&&(this.activeImage==this.imageArray.length-1&&this.startImage==0||this.activeImage+1==this.startImage))return this.end();this.activeImage==this.imageArray.length-1?this.changeImage(0):this.changeImage(this.activeImage+1)}},showPrev:function(){if(this.imageArray.length>1)this.activeImage==0?this.changeImage(this.imageArray.length-1):this.changeImage(this.activeImage-1)},showFirst:function(){this.imageArray.length>1&&this.changeImage(0)},
202
- showLast:function(){this.imageArray.length>1&&this.changeImage(this.imageArray.length-1)},enableKeyboardNav:function(){document.onkeydown=this.keyboardAction},disableKeyboardNav:function(){document.onkeydown=""},keyboardAction:function(a){keycode=a==null?event.keyCode:a.which;key=String.fromCharCode(keycode).toLowerCase();if(key=="x"||key=="o"||key=="c")Lightbox.end();else if(key=="p"||key=="%")Lightbox.showPrev();else if(key=="n"||key=="'")Lightbox.showNext();else if(key=="f")Lightbox.showFirst();
203
- else if(key=="l")Lightbox.showLast();else key=="s"&&Lightbox.imageArray.length>0&&Lightbox.options.enableSlideshow&&Lightbox.toggleSlideShow()},preloadNeighborImages:function(){var a=this.imageArray.length-1==this.activeImage?0:this.activeImage+1;nextImage=new Image;nextImage.src=this.imageArray[a].link;a=this.activeImage==0?this.imageArray.length-1:this.activeImage-1;prevImage=new Image;prevImage.src=this.imageArray[a].link},end:function(){this.disableKeyboardNav();this.pauseSlideShow();$(this.getID("lightbox")).hide();
204
- new Effect.Fade(this.getID("overlay"),{duration:this.overlayDuration});this.showBadObjects()},showBadObjects:function(){for(var a,b=Lightbox.badObjects,c=0;c<b.length;c++){a=document.getElementsByTagName(b[c]);for(var d=0;d<a.length;d++)$(a[d]).setStyle({visibility:"visible"})}},hideBadObjects:function(){for(var a,b=Lightbox.badObjects,c=0;c<b.length;c++){a=document.getElementsByTagName(b[c]);for(var d=0;d<a.length;d++)$(a[d]).setStyle({visibility:"hidden"})}},pause:function(a){var b=new Date;for(a=
205
- b.getTime()+a;;){b=new Date;if(b.getTime()>a)return}},getPageScroll:function(){var a,b;if(self.pageYOffset){a=self.pageXOffset;b=self.pageYOffset}else if(document.documentElement&&document.documentElement.scrollTop){a=document.documentElement.scrollLeft;b=document.documentElement.scrollTop}else if(document.body){a=document.body.scrollLeft;b=document.body.scrollTop}return{x:a,y:b}},getPageSize:function(){var a,b,c,d;if(window.innerHeight&&window.scrollMaxY){a=document.body.scrollWidth;b=window.innerHeight+
206
- window.scrollMaxY}else if(document.body.scrollHeight>document.body.offsetHeight){a=document.body.scrollWidth;b=document.body.scrollHeight}else{a=document.body.offsetWidth;b=document.body.offsetHeight}if(self.innerHeight){c=self.innerWidth;d=self.innerHeight}else if(document.documentElement&&document.documentElement.clientHeight){c=document.documentElement.clientWidth;d=document.documentElement.clientHeight}else if(document.body){c=document.body.clientWidth;d=document.body.clientHeight}return{pageWidth:a<
207
- c?c:a,pageHeight:b<d?d:b,winWidth:c,winHeight:d}},getID:function(a){return this.options.prefix+a}};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Simple Lightbox
3
+ * @author Archetyped (http://archetyped.com/tools/simple-lightbox/)
4
+ *
5
+ * Inspiration/History
6
+ * > 2010: Originally based on Lightbox Slideshow v1.1
7
+ * > by Justin Barkhuff (http://www.justinbarkhuff.com/lab/lightbox_slideshow/)
8
+ * > Largely based on Lightbox v2.02
9
+ * > by Lokesh Dhakar - http://huddletogether.com/projects/lightbox2/
10
+ * > 2011-01-12: Rebuilt as jQuery-compatible codebase
11
+ *
12
+ */
13
+
14
+
15
+ (function($) {
16
+ SLB = {
17
+ /* Properties */
18
+
19
+ //Core
20
+ prefix : 'slb',
21
+ options : {},
22
+ //Activation
23
+ trigger : null,
24
+ urls_checked : {},
25
+ //Items
26
+ items : [],
27
+ item_curr : null,
28
+ item_init : null,
29
+ group : null,
30
+ media : {},
31
+ //Content
32
+ content: {
33
+ caption_enabled: true,
34
+ caption_src: true,
35
+ desc_enabled: true,
36
+ labels : {
37
+ closeLink : 'close',
38
+ loadingMsg : 'loading',
39
+ nextLink : 'next &raquo;',
40
+ prevLink : '&laquo; prev',
41
+ startSlideshow : 'start slideshow',
42
+ stopSlideshow : 'stop slideshow',
43
+ numDisplayPrefix : 'Image',
44
+ numDisplaySeparator : 'of'
45
+ }
46
+ },
47
+ //Layout
48
+ container : document,
49
+ masks : ['select','object','embed','iframe'],
50
+ layout: {
51
+ template: '',
52
+ parsed: '',
53
+ placeholders : {
54
+ slbContent: '<img id="slb_slbContent" />',
55
+ slbLoading: '<span id="slb_slbLoading">loading</span>',
56
+ slbClose: '<a class="slb_slbClose" href="#">close</a>',
57
+ navPrev: '<a class="slb_navPrev slb_nav" href="#">&laquo; prev</a>',
58
+ navNext: '<a class="slb_navNext slb_nav" href="#">&raquo; next</a>',
59
+ navSlideControl: '<a class="slb_navSlideControl" href="#">Stop</a>',
60
+ dataCaption: '<span class="slb_dataCaption"></span>',
61
+ dataDescription: '<span class="slb_dataDescription"></span>',
62
+ dataNumber: '<span class="slb_dataNumber"></span>'
63
+ }
64
+ },
65
+ //Slideshow
66
+ slideshow: {
67
+ active: null,
68
+ enabled: true,
69
+ loop: true,
70
+ duration: 4,
71
+ timer: null
72
+ },
73
+ //Animation
74
+ anim: {
75
+ active: true,
76
+ overlay_duration: 0,
77
+ overlay_opacity: .8,
78
+ resize_duration: 0
79
+ },
80
+
81
+ /* Initialization */
82
+
83
+ /**
84
+ * Initialize lightbox instance
85
+ * @param object options Instance options
86
+ */
87
+ init: function(options) {
88
+ //Options
89
+ this.options = $.extend(true, {
90
+ //Core
91
+ prefix: null,
92
+
93
+ //Activation
94
+ trigger : null,
95
+ validateLinks : false,
96
+
97
+ //Content
98
+ captionEnabled: true,
99
+ captionSrc : true,
100
+ descEnabled: true,
101
+ labels : {},
102
+
103
+ //Layout
104
+ layout : null,
105
+ placeholders : {},
106
+
107
+ //Animation
108
+ animate : true,
109
+ overlayDuration : .2,
110
+ overlayOpacity : .8,
111
+ resizeSpeed : 400,
112
+
113
+ //Slideshow
114
+ autoPlay : true,
115
+ enableSlideshow : true,
116
+ loop : true,
117
+ slideTime : 4
118
+ }, options);
119
+
120
+ //Stop if no layout is defined
121
+ if (!this.options.layout || this.options.layout.toString().length == 0)
122
+ this.end();
123
+
124
+ /* Setup Properties */
125
+
126
+ //Prefix
127
+ if ( $.type(this.options.prefix) == 'string' && this.options.prefix.length > 0 ) {
128
+ this.prefix = this.options.prefix;
129
+ }
130
+
131
+ //Activation
132
+ this.trigger = ( $.isArray(this.options.trigger) ) ? this.options.trigger : [this.prefix];
133
+
134
+ //Content
135
+ $.extend(true, this.content, {
136
+ caption_enabled: !!this.options.captionEnabled,
137
+ caption_src: !!this.options.captionSrc,
138
+ desc_enabled: !!this.options.descEnabled,
139
+ labels: ( $.isPlainObject(this.options.labels) ) ? this.options.labels : {}
140
+ });
141
+
142
+ //Layout
143
+ $.extend(true, this.layout, {
144
+ template: this.options.layout,
145
+ placeholders: this.options.placeholders
146
+ });
147
+
148
+ //Animation
149
+ $.extend(this.anim, {
150
+ overlay_opacity: Math.max(Math.min(this.options.overlayOpacity,1),0)
151
+ });
152
+ if ( this.options.animate ) {
153
+ $.extend(this.anim, {
154
+ active: true,
155
+ overlay_duration: Math.max(this.options.overlayDuration,0),
156
+ resize_duration: this.options.resizeSpeed
157
+ });
158
+ } else {
159
+ $.extend(this.anim, {
160
+ active: false,
161
+ overlay_duration: 0,
162
+ resize_duration: 0
163
+ });
164
+ }
165
+
166
+ //Slideshow
167
+ $.extend(this.slideshow, {
168
+ play: !!this.options.autoPlay,
169
+ active: !!this.options.autoPlay,
170
+ enabled: !!this.options.enableSlideshow,
171
+ loop: ( !!this.options.enableSlideshow && !!this.options.loop ),
172
+ duration: ( $.isNumeric(this.options.slideTime) ) ? parseInt(this.options.slideTime) : 0
173
+ });
174
+
175
+ /* Init Layout */
176
+
177
+ var t = this;
178
+ var body = $('body');
179
+
180
+ //Overlay
181
+ $('<div/>', {
182
+ 'id': this.getID('overlay'),
183
+ 'css': {'display': 'none'}
184
+ }).appendTo(body)
185
+ .click(function() {t.end();});
186
+
187
+ //Viewer
188
+ var viewer = $('<div/>', {
189
+ 'id': this.getID('viewer'),
190
+ 'css': {'display': 'none'}
191
+ }).appendTo(body)
192
+ .click(function() {t.end();});
193
+
194
+ //Build layout from template
195
+ this.layout.parsed = this.getLayout();
196
+
197
+ //Insert layout into viewer
198
+ $(this.layout.parsed).appendTo(viewer);
199
+
200
+ //Set UI
201
+ this.initUI();
202
+
203
+ //Add events
204
+ this.initEvents();
205
+ },
206
+
207
+ /**
208
+ * Set localized values for UI elements
209
+ */
210
+ initUI: function() {
211
+ this.get('slbClose').html(this.getLabel('closeLink'));
212
+ this.get('navNext').html(this.getLabel('nextLink'));
213
+ this.get('navPrev').html(this.getLabel('prevLink'));
214
+ this.get('navSlideControl').html( this.getLabel((this.slideshowActive()) ? 'stopSlideshow' : 'startSlideshow') );
215
+ },
216
+
217
+ /**
218
+ * Add events to various UI elements
219
+ */
220
+ initEvents: function() {
221
+ var t = this, delay = 500;
222
+ //Remove all previous handlers
223
+ this.get('container,details,navPrev,navNext,navSlideControl,slbClose').unbind('click');
224
+
225
+ //Set event handlers
226
+ this.get('container,details').click(function(ev) {
227
+ ev.stopPropagation();
228
+ });
229
+
230
+ var clickP = function() {
231
+ //Handle double clicks
232
+ t.get('navPrev').unbind('click').click(false);
233
+ setTimeout(function() {t.get('navPrev').click(clickP);}, delay);
234
+ t.navPrev();
235
+ return false;
236
+ };
237
+ this.get('navPrev').click(function(){
238
+ return clickP();
239
+ });
240
+
241
+ var clickN = function() {
242
+ //Handle double clicks
243
+ t.get('navNext').unbind('click').click(false);
244
+ setTimeout(function() {t.get('navNext').click(clickN);}, delay);
245
+ t.navNext();
246
+ return false;
247
+ };
248
+ this.get('navNext').click(function() {
249
+ return clickN();
250
+ });
251
+
252
+ this.get('navSlideControl').click(function() {
253
+ t.slideshowToggle();
254
+ return false;
255
+ });
256
+ this.get('slbClose').click(function() {
257
+ t.end();
258
+ return false;
259
+ });
260
+
261
+ //Handle links on page
262
+ this.initLinks();
263
+ },
264
+
265
+ /**
266
+ * Finds all compatible image links on page
267
+ * @return void
268
+ */
269
+ initLinks: function() {
270
+ var ph = '{relattr}', t = this;
271
+ var sel = [], selBase = 'a[href][rel*="' + ph + '"]:not([rel~="' + this.addPrefix('off') + '"])';
272
+
273
+ //Click event handler
274
+ var handler = function() {
275
+ t.view(this);
276
+ return false;
277
+ };
278
+
279
+ //Build selector
280
+ for (var x = 0; x < this.trigger.length; x++) {
281
+ sel.push(selBase.replace(ph, this.trigger[x]));
282
+ }
283
+ sel = sel.join(',');
284
+ //Add event handler to links
285
+ $(sel, $(this.container)).live('click', handler);
286
+ },
287
+
288
+ /* Display */
289
+
290
+ /**
291
+ * Display viewer
292
+ * If item is part of a group, add other items in group
293
+ * @param node item Link element of item to display
294
+ */
295
+ view: function(item) {
296
+ item = $(item);
297
+ this.mask();
298
+
299
+ this.items = [];
300
+ this.setGroup(item);
301
+
302
+ var t = this;
303
+ var groupTemp = {};
304
+ this.fileExists(this.itemSource(item),
305
+ function() {
306
+ /* File Exists */
307
+
308
+ /* Handlers */
309
+
310
+ /**
311
+ * Add item to group
312
+ * @param obj el Item to add
313
+ * @param int idx DOM position index of item
314
+ * @return int Total number of items in group
315
+ */
316
+ var addItem = function(el, idx) {
317
+ groupTemp[idx] = el;
318
+ return groupTemp.length;
319
+ };
320
+
321
+ /**
322
+ * Build final item array & launch viewer
323
+ */
324
+ var proceed = function() {
325
+ t.item_init = 0;
326
+ //Sort links by document order
327
+ var order = [], el;
328
+ for (var x in groupTemp) {
329
+ order.push(x);
330
+ }
331
+ order.sort(function(a, b) { return (a - b); });
332
+ for (x = 0; x < order.length; x++) {
333
+ el = groupTemp[order[x]];
334
+ // Check if link being evaluated is the same as the clicked link
335
+ if ($(el).get(0) == $(item).get(0)) {
336
+ t.item_init = x;
337
+ }
338
+ t.items.push({'link':t.itemSource($(el)), 'title':t.getCaption(el), 'desc': t.getDescription(el)});
339
+ }
340
+ // Calculate top offset for the viewer and display
341
+ var vwrTop = $(document).scrollTop() + ($(window).height() / 15);
342
+ t.get('viewer').css('top', vwrTop + 'px').show();
343
+ t.itemLoad(t.item_init);
344
+ };
345
+
346
+ /* Display */
347
+
348
+ //Overlay
349
+ t.get('overlay')
350
+ .height($(document).height())
351
+ .fadeTo(t.anim.overlay_duration, t.options.overlayOpacity);
352
+
353
+ //Single item (not in group)
354
+ if ( !t.hasGroup() ) {
355
+ //Display item
356
+ t.item_init = 0;
357
+ addItem(item, t.item_init);
358
+ proceed();
359
+ } else {
360
+ //Item in group
361
+ var links = $(t.container).find('a');
362
+ //Get other items in group
363
+ var grpLinks = [];
364
+ var i, link;
365
+ for ( i = 0; i < links.length; i++ ) {
366
+ link = $(links[i]);
367
+ if ( t.itemSource(link) && t.inGroup(link) ) {
368
+ //Add links in group
369
+ grpLinks.push(link);
370
+ }
371
+ }
372
+
373
+ //Loop through group links, validate, and add to items array
374
+ var processed = 0;
375
+ for ( i = 0; i < grpLinks.length; i++ ) {
376
+ link = grpLinks[i];
377
+ t.fileExists(t.itemSource($(link)),
378
+ function(args) {
379
+ /* File exists */
380
+
381
+ //Add item
382
+ var el = args.items[args.idx];
383
+ addItem(el, args.idx);
384
+ processed++;
385
+ //Display valid items after all items parsed
386
+ if ( processed == args.items.length )
387
+ proceed();
388
+ },
389
+ function(args) {
390
+ /* File does not exist */
391
+ processed++;
392
+ //Display valid items after all items parsed
393
+ if (args.idx == args.items.length)
394
+ proceed();
395
+ },
396
+ {'idx': i, 'items': grpLinks});
397
+ }
398
+ }
399
+ },
400
+ function() {
401
+ /* File does not exist */
402
+ t.end();
403
+ });
404
+ },
405
+
406
+ /**
407
+ * Close the viewer
408
+ */
409
+ end: function() {
410
+ this.keyboardDisable();
411
+ this.slideshowPause();
412
+ this.get('viewer').hide();
413
+ this.get('overlay').fadeOut(this.anim.overlay_duration);
414
+ this.unmask();
415
+ },
416
+
417
+ /**
418
+ * Resizes viewer to fit image
419
+ * @param int imgWidth Image width in pixels
420
+ * @param int imgHeight Image height in pixels
421
+ */
422
+ viewerResize: function(w, h) {
423
+ var d = this.viewerSize(w, h);
424
+ //Resize container
425
+ this.get('container').animate({width: d.width, height: d.height}, this.anim.resize_duration);
426
+ //Resize overlay
427
+ this.get('overlay').css('min-width', d.width);
428
+ this.itemShow();
429
+ },
430
+
431
+ /**
432
+ * Retrieve or build container size
433
+ * @param int w Container width to set
434
+ * @param int h Container height to set
435
+ * @return obj Container width (w)/height (h) values
436
+ */
437
+ viewerSize: function(w, h) {
438
+ var style = 'padding';
439
+ var hz = ['left', 'right'], vt = ['top', 'bottom'];
440
+ var ph = 0, pv = 0;
441
+ var t = this;
442
+ //Calculate spacing around item
443
+
444
+ var getVal = function(prop) {
445
+ var unit = 'px';
446
+ prop = style + '-' + prop;
447
+ var ptemp = t.get('content').css( prop );
448
+ if ( ptemp.indexOf(unit) == -1 ) {
449
+ ptemp = 0;
450
+ } else {
451
+ ptemp = ptemp.replace(unit, '');
452
+ }
453
+ return ( $.isNumeric(ptemp) ) ? parseFloat(ptemp) : 0;
454
+ };
455
+
456
+ //Horizontal
457
+ for ( var x = 0; x < hz.length; x++) {
458
+ ph += getVal(hz[x]);
459
+ }
460
+ //Vertical
461
+ for ( x = 0; x < vt.length; x++) {
462
+ pv += getVal(vt[x]);
463
+ }
464
+ var c = {
465
+ 'width': w + ph,
466
+ 'height': h + pv
467
+ };
468
+ return c;
469
+ },
470
+
471
+ /**
472
+ * Displays objects that may conflict with the viewer
473
+ * @param bool show (optional) Whether or not to show objects (Default: TRUE)
474
+ */
475
+ unmask: function (show) {
476
+ show = ( typeof(show) == 'undefined' ) ? true : !!show;
477
+ var vis = (show) ? 'visible' : 'hidden';
478
+ $(this.masks.join(',')).css('visibility', vis);
479
+ },
480
+
481
+ /**
482
+ * Hides objects that may conflict with the viewer
483
+ * @uses unmask() to hide objects
484
+ */
485
+ mask: function () {
486
+ this.unmask(false);
487
+ },
488
+
489
+ /* Item */
490
+
491
+ /**
492
+ * Retrieve item property
493
+ * @uses items to retrieve property from item in array
494
+ * @param int idx Item position index
495
+ * @param string prop Item property to retrieve
496
+ * @return mixed Item property (Default: empty string)
497
+ */
498
+ itemProp: function(idx, prop) {
499
+ return ( idx < this.items.length && prop in this.items[idx] ) ? this.items[idx][prop] : '';
500
+ },
501
+
502
+ /**
503
+ * Preloads requested item prior to displaying it in viewer
504
+ * @param int idx Index of item in items property
505
+ * @uses items to retrieve item at specified index
506
+ * @uses viewerResize() to resize viewer after item has loaded
507
+ */
508
+ itemLoad: function(idx) {
509
+ this.item_curr = idx;
510
+
511
+ this.keyboardDisable();
512
+ this.slideshowPause();
513
+
514
+ //Hide elements during transition
515
+ this.get('slbLoading').show();
516
+ this.get('slbContent').hide();
517
+ this.get('details').hide();
518
+ var preloader = new Image();
519
+ var t = this;
520
+
521
+ //Event handler: Display item when loaded
522
+ $(preloader).bind('load', function() {
523
+ t.get('slbContent').attr('src', preloader.src);
524
+ t.viewerResize(preloader.width, preloader.height);
525
+
526
+ //Restart slideshow if active
527
+ if ( t.slideshowActive() )
528
+ t.slideshowStart();
529
+ });
530
+
531
+ //Load image
532
+ preloader.src = this.itemProp(this.item_curr, 'link');
533
+ },
534
+
535
+ /**
536
+ * Display image and begin preloading neighbors.
537
+ */
538
+ itemShow: function() {
539
+ this.get('slbLoading').hide();
540
+ var t = this;
541
+ this.get('slbContent').fadeIn(500, function() { t.contentUpdate(); });
542
+ if ( this.hasItems() ) {
543
+ this.itemPreloadSiblings();
544
+ }
545
+ },
546
+
547
+ /**
548
+ * Preloads items surrounding current item
549
+ */
550
+ itemPreloadSiblings: function() {
551
+ //Prev
552
+ var idxPrev = ( this.itemFirst() ) ? this.items.length - 1 : this.item_curr - 1;
553
+ var itemPrev = new Image();
554
+ itemPrev.src = this.itemProp(idxPrev, 'link');
555
+
556
+ //Next
557
+ var idxNext = ( this.itemLast() ) ? 0 : this.item_curr + 1;
558
+ if ( idxNext != idxPrev ) {
559
+ var itemNext = new Image();
560
+ itemNext.src = this.itemProp(idxNext, 'link');
561
+ }
562
+ },
563
+
564
+ /**
565
+ * Check if there is at least one image to display in the viewer
566
+ * @return bool TRUE if at least one image is found
567
+ * @uses items to check for images
568
+ */
569
+ hasItem: function() {
570
+ return ( this.items.length > 0 );
571
+ },
572
+
573
+ /**
574
+ * Check if there are multiple images to display in the viewer
575
+ * @return bool TRUE if there are multiple images
576
+ * @uses items to determine the number of images
577
+ */
578
+ hasItems: function() {
579
+ return ( this.items.length > 1 );
580
+ },
581
+
582
+ /**
583
+ * Check if the current image is the first image in the list
584
+ * @return bool TRUE if image is first
585
+ * @uses item_curr to check index of current image
586
+ */
587
+ itemFirst: function() {
588
+ return ( this.item_curr == 0 );
589
+ },
590
+
591
+ /**
592
+ * Check if the current image is the last image in the list
593
+ * @return bool TRUE if image is last
594
+ * @uses item_curr to check index of current image
595
+ * @uses items to compare current image to total number of images
596
+ */
597
+ itemLast: function() {
598
+ return ( this.item_curr == this.items.length - 1 );
599
+ },
600
+
601
+ /**
602
+ * Retrieve source URI in link
603
+ * @param obj el
604
+ * @return string Source file URI
605
+ */
606
+ itemSource: function(el) {
607
+ var src = $(el).attr('href');
608
+ var attr = $(el).attr('rel') || '';
609
+ if ( attr.length ) {
610
+ //Attachment source
611
+ var mSrc = this.mediaProp(el, 'source');
612
+ //Set source using extended properties
613
+ if ( $.type(mSrc) === 'string' && mSrc.length )
614
+ src = mSrc;
615
+ }
616
+ return src;
617
+ },
618
+
619
+ /**
620
+ * Check if current link is part of a gallery
621
+ * @param obj item
622
+ * @param string gType Gallery type to check for
623
+ * @return bool Whether link is part of a gallery
624
+ */
625
+ itemGallery: function(item, gType) {
626
+ var ret = false;
627
+ var galls = {
628
+ 'wp': '.gallery-icon',
629
+ 'ng': '.ngg-gallery-thumbnail'
630
+ };
631
+
632
+
633
+ if ( typeof gType == 'undefined' || !(gType in galls) ) {
634
+ gType = 'wp';
635
+ }
636
+ return ( $(item).parent(galls[gType]).length > 0 ) ? true : false ;
637
+ },
638
+
639
+ /* Media */
640
+
641
+ /**
642
+ * Retrieve ID of media item
643
+ * @param obj el Link element
644
+ * @return string|bool Media ID (Default: false - No ID)
645
+ */
646
+ mediaId: function(el) {
647
+ var h = $(el).attr('href');
648
+ if ($.type(h) !== 'string')
649
+ h = false;
650
+ return h;
651
+ },
652
+
653
+ /**
654
+ * Retrieve Media properties
655
+ * @param obj el Link element
656
+ * @return obj Properties for Media item (Default: Empty)
657
+ */
658
+ mediaProps: function(el) {
659
+ var props = {},
660
+ mId = this.mediaId(el);
661
+ if (mId && mId in this.media) {
662
+ props = this.media[mId];
663
+ }
664
+ return props;
665
+ },
666
+
667
+ /**
668
+ * Retrieve single property for media item
669
+ * @param obj el Image link DOM element
670
+ * @param string prop Property to retrieve
671
+ * @return mixed|null Item property (Default: NULL if property does not exist)
672
+ */
673
+ mediaProp: function(el, prop) {
674
+ var props = this.mediaProps(el);
675
+ return (prop in props) ? props[prop] : null;
676
+ },
677
+
678
+ /* Content */
679
+
680
+ /**
681
+ * Retrieve specified label
682
+ * @param string id Label ID
683
+ * @param string def (optional) Default value if specified label is invalid
684
+ * @return string Label text
685
+ */
686
+ getLabel: function(id, def) {
687
+ if ( typeof def == 'undefined' ) {
688
+ def = '';
689
+ }
690
+ return ( id in this.content.labels ) ? this.content.labels[id] : def;
691
+ },
692
+
693
+ /**
694
+ * Build caption for displayed item
695
+ * @param obj item DOM link element
696
+ * @return string Image caption
697
+ */
698
+ getCaption: function(item) {
699
+ item = $(item);
700
+ var caption = '';
701
+ if ( this.content.caption_enabled ) {
702
+ var sels = {
703
+ 'capt': '.wp-caption-text',
704
+ 'gIcon': '.gallery-icon'
705
+ };
706
+ var els = {
707
+ 'link': item,
708
+ 'origin': item,
709
+ 'sibs': null,
710
+ 'img': null
711
+ };
712
+ //WP Caption
713
+ if ( this.itemGallery(els.link, 'wp') ) {
714
+ els.origin = $(els.link).parent();
715
+ }
716
+ if ( (els.sibs = $(els.origin).siblings(sels.capt)) && $(els.sibs).length > 0 ) {
717
+ caption = $.trim($(els.sibs).first().text());
718
+ }
719
+
720
+ //Fall back to image properties
721
+ if ( !caption ) {
722
+ els.img = $(els.link).find('img').first();
723
+ if ( $(els.img).length ) {
724
+ //Image title / alt
725
+ caption = $(els.img).attr('title') || $(els.img).attr('alt');
726
+ caption = $.trim(caption);
727
+ }
728
+ }
729
+
730
+ //Media properties
731
+ if ( !caption ) {
732
+ caption = this.mediaProp(els.link, 'title') || '';
733
+ caption = $.trim(caption);
734
+ }
735
+
736
+ //Fall back Link Text
737
+ if ( !caption ) {
738
+ var c = '';
739
+ if ( ( c = $.trim($(els.link).text()) ) && c.length) {
740
+ caption = c;
741
+ } else if (this.options.captionSrc) {
742
+ //Fall back to Link href
743
+ caption = $(els.link).attr('href');
744
+ var trimChars = ['/', '#', '.'];
745
+ //Trim invalid characters
746
+ while ( caption.length && $.inArray(caption.charAt(0), trimChars) != -1 )
747
+ caption = caption.substr(1);
748
+ while ( caption.length && $.inArray(caption.charAt(caption.length - 1), trimChars) != -1 )
749
+ caption = caption.substr(0, caption.length - 1);
750
+
751
+ //Strip to base file name
752
+ var idx = caption.lastIndexOf('/');
753
+ if ( -1 != idx )
754
+ caption = caption.substr(idx + 1);
755
+ //Strip extension
756
+ idx = caption.lastIndexOf('.');
757
+ if ( -1 != idx ) {
758
+ caption = caption.slice(0, idx);
759
+ }
760
+ }
761
+ caption = $.trim(caption);
762
+ }
763
+ }
764
+ return caption;
765
+ },
766
+
767
+ /**
768
+ * Retrieve item description
769
+ * @param obj item
770
+ * @return string Item description (Default: empty string)
771
+ */
772
+ getDescription: function(item) {
773
+ var desc = '';
774
+ if ( this.content.desc_enabled ) {
775
+ //Retrieve description
776
+ if ( this.itemGallery(item, 'ng') ) {
777
+ desc = $(item).attr('title');
778
+ } else {
779
+ desc = this.mediaProp(item, 'desc');
780
+ }
781
+
782
+ if (!desc)
783
+ desc = '';
784
+ }
785
+ return desc;
786
+ },
787
+
788
+ /**
789
+ * Display item details
790
+ */
791
+ contentUpdate: function() {
792
+ //Caption
793
+ if (this.content.caption_enabled) {
794
+ this.get('dataCaption').text(this.itemProp(this.item_curr, 'title'));
795
+ this.get('dataCaption').show();
796
+ } else {
797
+ this.get('dataCaption').hide();
798
+ }
799
+
800
+ //Description
801
+ this.get('dataDescription').html(this.itemProp(this.item_curr, 'desc'));
802
+
803
+ //Handle grouped items
804
+ if ( this.hasItems() ) {
805
+ var num_display = this.getLabel('numDisplayPrefix') + ' ' + (this.item_curr + 1) + ' ' + this.getLabel('numDisplaySeparator') + ' ' + this.items.length;
806
+ this.get('dataNumber')
807
+ .text(num_display)
808
+ .show();
809
+ }
810
+
811
+ //Resize content area
812
+ this.get('details').width(this.get('container').width());
813
+ this.navUpdate();
814
+ var t = this;
815
+ this.get('details').animate({height: 'show', opacity: 'show'}, 650);
816
+ },
817
+
818
+ /* Layout */
819
+
820
+ /**
821
+ * Build layout from template
822
+ * @uses options.layout
823
+ * @return string Layout markup (HTML)
824
+ */
825
+ getLayout: function() {
826
+ var l = this.layout.template;
827
+
828
+ //Expand placeholders
829
+ var ph, phs, phr;
830
+ for ( ph in this.layout.placeholders ) {
831
+ phs = '{' + ph + '}';
832
+ //Continue to next placeholder if current one is not in layout
833
+ if (l.indexOf(phs) == -1)
834
+ continue;
835
+ phr = new RegExp(phs, "g");
836
+ l = l.replace(phr, this.layout.placeholders[ph]);
837
+ }
838
+
839
+ //Return final layout
840
+ return l;
841
+
842
+ },
843
+
844
+ /* Grouping */
845
+
846
+ /**
847
+ * Sets group based on current item
848
+ * @param obj item DOM element to get group from
849
+ */
850
+ setGroup: function(item) {
851
+ this.group = this.getGroup(item);
852
+ },
853
+
854
+ /**
855
+ * Extract group name from
856
+ * @param obj item Element to extract group name from
857
+ * @return string Group name
858
+ */
859
+ getGroup: function(item) {
860
+ //Return global group property if no item specified
861
+ if ( typeof item == 'undefined' || 0 == $(item).length )
862
+ return this.group;
863
+ //Get item's group
864
+ var g = '';
865
+ var attr = $(item).attr('rel') || '';
866
+ if ( attr != '' ) {
867
+ var gTmp = '',
868
+ gSt = '[',
869
+ gEnd = ']',
870
+ search = this.addPrefix('group') + gSt,
871
+ idx,
872
+ prefix = ' ';
873
+ //Find group indicator
874
+ idx = attr.indexOf(search);
875
+ //Prefix with space to find whole word
876
+ if ( prefix != search.charAt(0) && idx > 0 ) {
877
+ search = prefix + search;
878
+ idx = attr.indexOf(search);
879
+ }
880
+ //Continue processing if value is found
881
+ if ( idx != -1 ) {
882
+ //Extract group name
883
+ gTmp = $.trim(attr.substring(idx).replace(search, ''));
884
+ //Check if group defined
885
+ if (gTmp.length > 1 && gTmp.indexOf(gEnd) > 0) {
886
+ //Extract group name
887
+ g = gTmp.substring(0, gTmp.indexOf(gEnd));
888
+ }
889
+ }
890
+ }
891
+ return g;
892
+ },
893
+
894
+ /**
895
+ * Check if item is part of current group
896
+ * @param obj item Item to check
897
+ * @return bool TRUE if item is in current group, FALSE otherwise
898
+ */
899
+ inGroup: function(item) {
900
+ return ( this.hasGroup() && ( this.getGroup(item) == this.getGroup() ) ) ? true : false;
901
+ },
902
+
903
+ /**
904
+ * Check if group is set
905
+ * @return bool TRUE if group is set, FALSE otherwise
906
+ */
907
+ hasGroup: function() {
908
+ return ( $.type(this.group) == 'string' && this.group.length > 0 ) ? true : false;
909
+ },
910
+
911
+ /* Slideshow */
912
+
913
+ /**
914
+ * Checks if slideshow is currently activated
915
+ * @return bool TRUE if slideshow is active, false otherwise
916
+ * @uses slideshow.active to check slideshow activation status
917
+ */
918
+ slideshowActive: function() {
919
+ return this.slideshow.active;
920
+ },
921
+
922
+ /**
923
+ * Start the slideshow
924
+ */
925
+ slideshowStart: function() {
926
+ this.slideshow.active = true;
927
+ var t = this;
928
+ clearInterval(this.slideshow.timer);
929
+ this.slideshow.timer = setInterval(function() { t.navNext(); t.slideshowPause(); }, this.slideshow.duration * 1000);
930
+ this.get('navSlideControl').text(this.getLabel('stopSlideshow'));
931
+ },
932
+
933
+ /**
934
+ * Stop the slideshow
935
+ */
936
+ slideshowStop: function() {
937
+ this.slideshow.active = false;
938
+ if ( this.slideshow.timer ) {
939
+ clearInterval(this.slideshow.timer);
940
+ }
941
+ this.get('navSlideControl').text(this.getLabel('startSlideshow'));
942
+ },
943
+
944
+ /**
945
+ * Toggles the slideshow status
946
+ */
947
+ slideshowToggle: function() {
948
+ if ( this.slideshowActive() ) {
949
+ this.slideshowStop();
950
+ } else {
951
+ this.slideshowStart();
952
+ }
953
+ },
954
+
955
+ /**
956
+ * Pauses the slideshow
957
+ * Stops the slideshow but does not change the slideshow's activation status
958
+ */
959
+ slideshowPause: function() {
960
+ if ( this.slideshow.timer ) {
961
+ clearInterval(this.slideshow.timer);
962
+ }
963
+ },
964
+
965
+ /* Navigation */
966
+
967
+ /**
968
+ * Display appropriate previous and next hover navigation.
969
+ */
970
+ navUpdate: function() {
971
+ if ( this.hasItems() ) {
972
+ this.get('navPrev').show();
973
+ this.get('navNext').show();
974
+ if ( this.slideshow.enabled ) {
975
+ this.get('navSlideControl').show();
976
+ if ( this.slideshowActive() ) {
977
+ this.slideshowStart();
978
+ } else {
979
+ this.slideshowStop();
980
+ }
981
+ } else {
982
+ this.get('navSlideControl').hide();
983
+ }
984
+ } else {
985
+ // Hide navigation controls when only one image exists
986
+ this.get('dataNumber').hide();
987
+ this.get('navPrev').hide();
988
+ this.get('navNext').hide();
989
+ this.get('navSlideControl').hide();
990
+ }
991
+ this.keyboardEnable();
992
+ },
993
+
994
+ /**
995
+ * Show the next image in the list
996
+ */
997
+ navNext : function() {
998
+ if ( this.hasItems() ) {
999
+ if ( !this.slideshow.loop && this.itemLast() ) {
1000
+ return this.end();
1001
+ }
1002
+ if ( this.itemLast() ) {
1003
+ this.navFirst();
1004
+ } else {
1005
+ this.itemLoad(this.item_curr + 1);
1006
+ }
1007
+ }
1008
+ },
1009
+
1010
+ /**
1011
+ * Show the previous image in the list
1012
+ */
1013
+ navPrev : function() {
1014
+ if ( this.hasItems() ) {
1015
+ if ( !this.slideshow.loop && this.itemFirst() ) {
1016
+ return this.end();
1017
+ }
1018
+ if ( this.itemFirst() ) {
1019
+ this.navLast();
1020
+ } else {
1021
+ this.itemLoad(this.item_curr - 1);
1022
+ }
1023
+ }
1024
+ },
1025
+
1026
+ /**
1027
+ * Show the first image in the list
1028
+ */
1029
+ navFirst : function() {
1030
+ if ( this.hasItems() ) {
1031
+ this.itemLoad(0);
1032
+ }
1033
+ },
1034
+
1035
+ /**
1036
+ * Show the last image in the list
1037
+ */
1038
+ navLast : function() {
1039
+ if ( this.hasItems() ) {
1040
+ this.itemLoad(this.items.length - 1);
1041
+ }
1042
+ },
1043
+
1044
+ /**
1045
+ * Enable image navigation via the keyboard
1046
+ */
1047
+ keyboardEnable: function() {
1048
+ var t = this;
1049
+ $(document).keydown(function(e) {
1050
+ t.keyboardControl(e);
1051
+ });
1052
+ },
1053
+
1054
+ /**
1055
+ * Disable image navigation via the keyboard
1056
+ */
1057
+ keyboardDisable: function() {
1058
+ $(document).unbind('keydown');
1059
+ },
1060
+
1061
+ /**
1062
+ * Handler for keyboard events
1063
+ * @param event e Keyboard event data
1064
+ */
1065
+ keyboardControl: function(e) {
1066
+ var code = e.which;
1067
+ var key = String.fromCharCode(code).toLowerCase();
1068
+
1069
+ if ( code == 27 || key == 'x' ) {
1070
+ //Close
1071
+ this.end();
1072
+ } else if ( code == 39 || key =='n' ) {
1073
+ //Next
1074
+ this.navNext();
1075
+ } else if ( code == 37 || key == 'p' ) {
1076
+ //Previous
1077
+ this.navPrev();
1078
+ }
1079
+ },
1080
+
1081
+ /* Helpers */
1082
+
1083
+ /**
1084
+ * Generate separator text
1085
+ * @param string sep Separator text
1086
+ * @return string Separator text
1087
+ */
1088
+ getSep: function(sep) {
1089
+ return ( typeof sep == 'undefined' ) ? '_' : sep;
1090
+ },
1091
+
1092
+ /**
1093
+ * Retrieve prefix
1094
+ * @return string Object prefix
1095
+ */
1096
+ getPrefix: function() {
1097
+ return this.prefix;
1098
+ },
1099
+
1100
+ /**
1101
+ * Add prefix to text
1102
+ * @param string txt Text to add prefix to
1103
+ * @param string sep (optional) Separator text
1104
+ * @return string Prefixed text
1105
+ */
1106
+ addPrefix: function(txt, sep) {
1107
+ return this.getPrefix() + this.getSep(sep) + txt;
1108
+ },
1109
+
1110
+ hasPrefix: function(txt) {
1111
+ return ( txt.indexOf(this.addPrefix('')) === 0 ) ? true : false;
1112
+ },
1113
+
1114
+ /**
1115
+ * Generate formatted ID for viewer-specific elements
1116
+ * @param string id Base ID of element
1117
+ * @return string Formatted ID
1118
+ */
1119
+ getID: function(id) {
1120
+ return this.addPrefix(id);
1121
+ },
1122
+
1123
+ /**
1124
+ * Generate formatted selector for viewer-specific elements
1125
+ * Compares specified ID to placeholders first, then named elements
1126
+ * Multiple selectors can be included and separated by commas (',')
1127
+ * @param string id Base ID of element
1128
+ * @uses layout.placeholders to compare id to placeholder names
1129
+ * @return string Formatted selector
1130
+ */
1131
+ getSel: function(id) {
1132
+ //Process multiple selectors
1133
+ var delim = ',', prefix = '#', sel;
1134
+ if (id.toString().indexOf(delim) != -1) {
1135
+ //Split
1136
+ var sels = id.toString().split(delim);
1137
+ //Build selector
1138
+ for (var x = 0; x < sels.length; x++) {
1139
+ sels[x] = this.getSel($.trim(sels[x]));
1140
+ }
1141
+ //Join
1142
+ sel = sels.join(delim);
1143
+ } else {
1144
+ //Single selector
1145
+ if ( id in this.layout.placeholders ) {
1146
+ var ph = $( this.layout.placeholders[id] );
1147
+ if (!ph.attr('id')) {
1148
+ //Class selector
1149
+ prefix = '.';
1150
+ }
1151
+ }
1152
+ sel = prefix + this.getID(id);
1153
+ }
1154
+
1155
+ return sel;
1156
+ },
1157
+
1158
+ /**
1159
+ * Retrieve viewer-specific element
1160
+ * @param string id Base ID of element
1161
+ * @uses getSel() to generate formatted selector for element
1162
+ * @return object jQuery object of selected element(s)
1163
+ */
1164
+ get: function(id) {
1165
+ return $(this.getSel(id));
1166
+ },
1167
+
1168
+ /**
1169
+ * Checks if file exists using AJAX request
1170
+ * @param string url File URL
1171
+ * @param callback success Callback to run if file exists
1172
+ * @param callback failure Callback to run if file does not exist
1173
+ * @param obj args Arguments for callback
1174
+ */
1175
+ fileExists: function(url, success, failure, args) {
1176
+ //Validate
1177
+ var t = this;
1178
+ if ( !$.isPlainObject(args) ) {
1179
+ args = null;
1180
+ }
1181
+ if ( !$.isFunction(failure) ) {
1182
+ failure = function() {
1183
+ t.end();
1184
+ };
1185
+ }
1186
+ if ( !$.isFunction(success) ) {
1187
+ success = failure;
1188
+ }
1189
+
1190
+ //Immediate success when validation disabled
1191
+ if ( !this.options.validateLinks ) {
1192
+ return success(args);
1193
+ }
1194
+
1195
+ //Validate link
1196
+ var statusFail = 400;
1197
+ var stateCheck = 4;
1198
+ var proceed = function(res) {
1199
+ if ( res.status < statusFail ) {
1200
+ success(args);
1201
+ } else {
1202
+ failure(args);
1203
+ }
1204
+ };
1205
+
1206
+ //Check if URL already processed
1207
+ if ( url in this.urls_checked ) {
1208
+ proceed(this.urls_checked[url]);
1209
+ } else {
1210
+ //Build AJAX request to check new file
1211
+ var req = new XMLHttpRequest();
1212
+ req.open('HEAD', url, true);
1213
+ req.onreadystatechange = function() {
1214
+ if ( stateCheck == this.readyState ) {
1215
+ t.addUrl(url, this);
1216
+ proceed(this);
1217
+ }
1218
+ };
1219
+ req.send();
1220
+ }
1221
+ },
1222
+
1223
+ addUrl: function(url, res) {
1224
+ if (!(url in this.urls_checked))
1225
+ this.urls_checked[url] = res;
1226
+ }
1227
+ };
1228
+ })(jQuery);
l10n/simple-lightbox.pot ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2010
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: \n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/simple-lightbox\n"
7
+ "POT-Creation-Date: 2011-10-14 03:38:17+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: 2010-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: model.php:88
16
+ msgid "Activation"
17
+ msgstr ""
18
+
19
+ #: model.php:89
20
+ msgid "Grouping"
21
+ msgstr ""
22
+
23
+ #: model.php:90
24
+ msgid "UI"
25
+ msgstr ""
26
+
27
+ #: model.php:91
28
+ msgid "Labels"
29
+ msgstr ""
30
+
31
+ #: model.php:94
32
+ msgid "Enable Lightbox Functionality"
33
+ msgstr ""
34
+
35
+ #: model.php:95
36
+ msgid "Enable on Home page"
37
+ msgstr ""
38
+
39
+ #: model.php:96
40
+ msgid "Enable on Posts"
41
+ msgstr ""
42
+
43
+ #: model.php:97
44
+ msgid "Enable on Pages"
45
+ msgstr ""
46
+
47
+ #: model.php:98
48
+ msgid "Enable on Archive Pages (tags, categories, etc.)"
49
+ msgstr ""
50
+
51
+ #: model.php:99
52
+ msgid "Enable backwards-compatibility with legacy lightbox links"
53
+ msgstr ""
54
+
55
+ #: model.php:100
56
+ msgid "Activate image attachment links"
57
+ msgstr ""
58
+
59
+ #: model.php:101
60
+ msgid "Validate links"
61
+ msgstr ""
62
+
63
+ #: model.php:102
64
+ msgid "Group image links (for displaying as a slideshow)"
65
+ msgstr ""
66
+
67
+ #: model.php:103
68
+ msgid "Group image links by Post (e.g. on pages with multiple posts)"
69
+ msgstr ""
70
+
71
+ #: model.php:104
72
+ msgid "Group gallery links separately"
73
+ msgstr ""
74
+
75
+ #: model.php:105
76
+ msgid "Theme"
77
+ msgstr ""
78
+
79
+ #: model.php:106
80
+ msgid "Animate lightbox resizing"
81
+ msgstr ""
82
+
83
+ #: model.php:107
84
+ msgid "Automatically Start Slideshow"
85
+ msgstr ""
86
+
87
+ #: model.php:108
88
+ msgid "Slide Duration (Seconds)"
89
+ msgstr ""
90
+
91
+ #: model.php:109
92
+ msgid "Loop through images"
93
+ msgstr ""
94
+
95
+ #: model.php:110
96
+ msgid "Overlay Opacity (0 - 1)"
97
+ msgstr ""
98
+
99
+ #: model.php:111
100
+ msgid "Enable caption"
101
+ msgstr ""
102
+
103
+ #: model.php:112
104
+ msgid "Use image URI as caption when link title not set"
105
+ msgstr ""
106
+
107
+ #: model.php:113
108
+ msgid "Enable description"
109
+ msgstr ""
110
+
111
+ #: model.php:114
112
+ msgid "Close link (for accessibility only, image used for button)"
113
+ msgstr ""
114
+
115
+ #: model.php:115
116
+ msgid "Loading indicator"
117
+ msgstr ""
118
+
119
+ #: model.php:116
120
+ msgid "Next Image link"
121
+ msgstr ""
122
+
123
+ #: model.php:117
124
+ msgid "Previous Image link"
125
+ msgstr ""
126
+
127
+ #: model.php:118
128
+ msgid "Start Slideshow link"
129
+ msgstr ""
130
+
131
+ #: model.php:119
132
+ msgid "Stop Slideshow link"
133
+ msgstr ""
134
+
135
+ #: model.php:120
136
+ msgid "Image number prefix (e.g. <strong>Image</strong> x of y)"
137
+ msgstr ""
138
+
139
+ #: model.php:121
140
+ msgid "Image number separator (e.g. Image x <strong>of</strong> y)"
141
+ msgstr ""
142
+
143
+ #: model.php:728
144
+ msgid "Settings"
145
+ msgstr ""
146
+
147
+ #: model.php:729
148
+ msgid "Reset"
149
+ msgstr ""
150
+
151
+ #: model.php:730
152
+ msgid "Are you sure you want to reset your settings?"
153
+ msgstr ""
154
+
155
+ #: model.php:746
156
+ msgid "You do not have sufficient permissions to manage plugins for this blog."
157
+ msgstr ""
158
+
159
+ #: model.php:796
160
+ msgid "Lightbox Settings"
161
+ msgstr ""
main.php CHANGED
@@ -2,13 +2,13 @@
2
  /*
3
  Plugin Name: Simple Lightbox
4
  Plugin URI: http://archetyped.com/tools/simple-lightbox/
5
- Description: Customizable Lightbox for Wordpress
6
- Version: 1.3.1
7
  Author: Archetyped
8
  Author URI: http://archetyped.com
9
  */
10
  /*
11
- Copyright 2010 Solomon Marchessault (wp@archetyped.com)
12
 
13
  This program is free software; you can redistribute it and/or
14
  modify it under the terms of the GNU General Public License
@@ -24,14 +24,18 @@ You should have received a copy of the GNU General Public License
24
  along with this program; if not, write to the Free Software
25
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26
  */
27
-
28
  require_once 'model.php';
29
 
30
- $slb =& new SLB_Lightbox();
31
 
32
  function slb_enabled() {
33
  global $slb;
34
  return $slb->is_enabled();
35
  }
36
 
 
 
 
 
 
37
  ?>
2
  /*
3
  Plugin Name: Simple Lightbox
4
  Plugin URI: http://archetyped.com/tools/simple-lightbox/
5
+ Description: Highly Customizable Lightbox for Wordpress
6
+ Version: 1.6.3.1
7
  Author: Archetyped
8
  Author URI: http://archetyped.com
9
  */
10
  /*
11
+ Copyright 2011 Solomon Marchessault (contact@archetyped.com)
12
 
13
  This program is free software; you can redistribute it and/or
14
  modify it under the terms of the GNU General Public License
24
  along with this program; if not, write to the Free Software
25
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26
  */
 
27
  require_once 'model.php';
28
 
29
+ $slb = new SLB_Lightbox();
30
 
31
  function slb_enabled() {
32
  global $slb;
33
  return $slb->is_enabled();
34
  }
35
 
36
+ function slb_register_theme($name, $title, $stylesheet_url, $layout) {
37
+ global $slb;
38
+ $slb->register_theme($name, $title, $stylesheet_url, $layout);
39
+ }
40
+
41
  ?>
model.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
 
3
  require_once 'includes/class.base.php';
 
4
 
5
  /**
6
  * Lightbox functionality class
@@ -11,6 +12,14 @@ class SLB_Lightbox extends SLB_Base {
11
 
12
  /*-** Properties **-*/
13
 
 
 
 
 
 
 
 
 
14
  /**
15
  * Page that plugin options are on
16
  * @var string
@@ -24,38 +33,82 @@ class SLB_Lightbox extends SLB_Base {
24
  var $options_admin_form = 'options.php';
25
 
26
  /**
27
- * Default options
28
- * 0: Value
29
- * 1: Label
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  * @var array
31
  */
32
- var $options_default = array (
33
- 'header_enabled' => 'Activation',
34
- 'enabled' => array(true, 'Enable Lightbox Functionality'),
35
- 'enabled_home' => array(true, 'Enable on Home page'),
36
- 'enabled_single' => array(true, 'Enable on Single Posts/Pages'),
37
- 'enabled_archive' => array(true, 'Enable on Archive Pages (tags, categories, etc.)'),
38
- 'activate_links' => array(true, 'Activate all image links on page'),
39
- 'header_activation' => 'Grouping',
40
- 'group_links' => array(true, 'Group automatically activated links (for displaying as a slideshow)'),
41
- 'group_post' => array(true, 'Group image links by Post (e.g. on pages with multiple posts)'),
42
- 'header_ui' => 'UI',
43
- 'autostart' => array(true, 'Automatically Start Slideshow'),
44
- 'duration' => array(6, 'Slide Duration (Seconds)', array('size' => 3, 'maxlength' => 3)),
45
- 'loop' => array(true, 'Loop through images'),
46
- 'overlay_opacity' => array(0.8, 'Overlay Opacity (0 - 1)', array('size' => 3, 'maxlength' => 3)),
47
- 'header_strings' => 'Labels',
48
- 'txt_closeLink' => array('close', 'Close link (for accessibility only, image used for button)'),
49
- 'txt_loadingMsg' => array('loading', 'Loading indicator'),
50
- 'txt_nextLink' => array('next &raquo;', 'Next Image link'),
51
- 'txt_prevLink' => array('&laquo; prev', 'Previous Image link'),
52
- 'txt_startSlideshow' => array('start slideshow', 'Start Slideshow link'),
53
- 'txt_stopSlideshow' => array('stop slideshow', 'Stop Slideshow link'),
54
- 'txt_numDisplayPrefix' => array('Image', 'Image number prefix (e.g. <strong>Image</strong> x of y)'),
55
- 'txt_numDisplaySeparator' => array('of', 'Image number separator (e.g. Image x <strong>of</strong> y)')
56
- );
57
-
58
- /*-** Init **-*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  function SLB_Lightbox() {
61
  $this->__construct();
@@ -64,15 +117,99 @@ class SLB_Lightbox extends SLB_Base {
64
  function __construct() {
65
  parent::__construct();
66
  $this->init();
 
 
 
 
67
  }
68
 
69
- function init() {
70
- $this->register_hooks();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
  function register_hooks() {
74
- register_activation_hook($this->util->get_plugin_base_file(), $this->m('activate'));
 
75
  /* Admin */
 
76
  //Init lightbox admin
77
  add_action('admin_init', $this->m('admin_settings'));
78
  //Enqueue header files (CSS/JS)
@@ -80,34 +217,50 @@ class SLB_Lightbox extends SLB_Base {
80
  //Reset Settings
81
  add_action('admin_action_' . $this->add_prefix('reset'), $this->m('admin_reset'));
82
  add_action('admin_notices', $this->m('admin_notices'));
 
 
 
 
83
 
84
  /* Client-side */
85
- //Init lightbox (client-side)
 
 
86
  add_action('wp_enqueue_scripts', $this->m('enqueue_files'));
87
  add_action('wp_head', $this->m('client_init'));
88
- add_filter('plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m('admin_plugin_action_links'), 10, 4);
89
- add_filter('the_content', $this->m('activate_post_links'));
90
- }
91
-
92
- function activate() {
93
- //Set default options (if not yet set)
94
- $this->reset_options(false);
 
 
 
 
 
 
95
  }
 
 
96
 
 
 
97
  /**
98
- * Resets option values to their default values
99
- * @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
 
 
100
  */
101
- function reset_options($hard = true) {
102
- foreach ( $this->options_default as $id => $data ) {
103
- $opt = $this->add_prefix($id);
104
- if ( !$hard && !is_null(get_option($opt, null)) ) {
105
- continue;
106
- }
107
- update_option($opt, $data[0]);
108
- }
109
  }
110
-
111
  /*-** Helpers **-*/
112
 
113
  /**
@@ -115,127 +268,797 @@ class SLB_Lightbox extends SLB_Base {
115
  * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
116
  */
117
  function is_enabled($check_request = true) {
118
- $ret = ( get_option($this->add_prefix('enabled')) ) ? true : false;
119
  if ( $ret && $check_request ) {
120
  $opt = '';
121
  //Determine option to check
122
  if ( is_home() )
123
  $opt = 'home';
124
- elseif ( is_single() )
125
- $opt = 'single';
 
126
  elseif ( is_archive() || is_search() )
127
  $opt = 'archive';
128
  //Check option
129
- if ( ! empty($opt) && ( $opt = 'enabled_' . $opt ) && isset($this->options_default[$opt]) ) {
130
- $ret = ( get_option($this->add_prefix($opt)) ) ? true : false;
131
  }
132
  }
133
  return $ret;
134
  }
135
 
 
 
136
  /**
137
- * Builds object of option data
138
- * Properties:
139
- * > id: Option ID
140
- * > value: Option's value (uses default value if option not yet set)
141
- * > value_default: Option's default value (formatted)
142
- *
143
- * @param string $option Option name
144
- * @return object Option data
145
- */
146
- function get_option($option) {
147
- $ret = new stdClass();
148
- $ret->id = $this->add_prefix($option);
149
- $ret->value = get_option($ret->id, $this->get_default_value($option, false));
150
- $ret->value_default = $this->get_default_value($option, false);
151
- $ret->value_default_formatted = $this->get_default_value($option);
152
- $ret->attr = $this->get_default_attr($option);
153
- return $ret;
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
155
 
156
  /**
157
- * Retrieve an option's value
158
- * @param string $option Option name
159
- * @return mixed Option value
 
 
 
 
 
 
160
  */
161
- function get_option_value($option) {
162
- $opt = $this->get_option($option);
163
- return $opt->value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
 
166
  /**
167
- * Retrieve default attributes for an option
168
- * @param string $option Option name
169
- * @return array Default attributes
 
170
  */
171
- function get_default_attr($option) {
172
- $ret = array();
173
- if ( isset($this->options_default[$option][2]) )
174
- $ret = $this->options_default[$option][2];
175
- return $ret;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
178
  /**
179
- * Retrieve default value for specified option
180
- * @param string $option Option name
181
- * @param bool $formatted Whether to return formatted value (e.g. for use in admin UI)
182
- * @return mixed Option default value
183
  */
184
- function get_default_value($option, $formatted = true) {
185
- $ret = '';
186
- if ( isset($this->options_default[$option][0]) ) {
187
- $ret = $this->options_default[$option][0];
188
- //Format value (if required)
189
- if ( $formatted ) {
190
- if ( is_bool($ret) || ( is_string($ret) && 'on' == $ret ) )
191
- $ret = ( $ret ) ? 'Enabled' : 'Disabled';
192
- if ( is_numeric($ret) )
193
- $ret = strval($ret);
194
- $ret = htmlentities($ret);
195
- }
196
- } elseif ( ! is_array($this->options_default[$option]) ) {
197
- $ret = $this->options_default[$option];
198
  }
199
- return $ret;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
 
202
  /*-** Frontend **-*/
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  /**
205
  * Scans post content for image links and activates them
206
  *
207
  * Lightbox will not be activated for feeds
208
  * @param $content
 
209
  */
210
- function activate_post_links($content) {
211
- //Check option
212
- if ( ! is_feed() && $this->is_enabled() && $this->get_option_value('activate_links') && $this->get_option_value('group_links') && $this->get_option_value('group_post') ) {
213
- //Scan for links
214
- $matches = array();
215
- if ( preg_match_all("/\<a[^\>]*href=[^\s]+\.(?:jp[e]*g|gif|png).*?\>/i", $content, $matches) ) {
216
- global $post;
217
- //Iterate through links & add lightbox if necessary
218
- foreach ($matches[0] as $link) {
219
- //Check if rel attribute exists
220
- $link_new = $link;
221
- $rel = '';
222
- if ( strpos(strtolower($link_new), ' rel=') !== false && preg_match("/\s+rel=(?:\"|')(.*?)(?:\"|')(\s|\>)/i", $link_new, $rel) ) {
223
- //Check if lightbox is already set in rel attribute
224
- $link_new = str_replace($rel[0], $rel[2], $link_new);
225
- $rel = $rel[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  }
227
 
228
- if ( strpos($rel, 'lightbox') === false) {
229
- //Add rel attribute to link
230
- $rel .= ' lightbox[' . $this->add_prefix($post->ID) . ']';
231
- $link_new = '<a rel="' . $rel . '"' . substr($link_new,2);
232
- //Insert modified link
233
- $content = str_replace($link, $link_new, $content);
234
  }
 
 
 
235
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
237
  }
238
- return $content;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
 
241
  /**
@@ -244,8 +1067,11 @@ class SLB_Lightbox extends SLB_Base {
244
  function enqueue_files() {
245
  if ( ! $this->is_enabled() )
246
  return;
247
- wp_enqueue_script($this->add_prefix('lib'), $this->util->get_file_url('js/lib.js'));
248
- wp_enqueue_style($this->add_prefix('lightbox_css'), $this->util->get_file_url('css/lightbox.css'));
 
 
 
249
  }
250
 
251
  /**
@@ -255,58 +1081,216 @@ class SLB_Lightbox extends SLB_Base {
255
  function client_init() {
256
  if ( ! $this->is_enabled() )
257
  return;
258
-
259
  $options = array();
260
  $out = array();
261
- $out['script_start'] = '<script type="text/javascript">Event.observe(window,"load",function(){';
262
- $out['script_end'] = '});</script>';
263
  $js_code = array();
264
- //Activate links on page
265
- if ( $this->get_option_value('activate_links') ) {
266
- $rel = ( $this->get_option_value('group_links') ) ? 'lightbox[' . $this->get_prefix() . ']' : 'lightbox';
267
- ob_start();
268
- ?>
269
- $$('a[href$=".jpg"]:not([rel~="lightbox"])','a[href$=".jpeg"]:not([rel~="lightbox"])','a[href$=".gif"]:not([rel~="lightbox"])','a[href$=".png"]:not([rel~="lightbox"])').each(function(el){if (! /(^|\b)lightbox\[.+\]($|\b)/i.test(el.rel)){var rel=(el.rel.length > 0) ? el.rel + ' ' : '';el.rel=rel + '<?php echo $rel; ?>';}});
270
- <?php
271
- $js_code[] = ob_get_clean();
272
- }
273
  //Get options
274
  $options = array(
275
- 'autoPlay' => $this->get_option_value('autostart'),
276
- 'slideTime' => $this->get_option_value('duration'),
277
- 'loop' => $this->get_option_value('loop'),
278
- 'overlayOpacity' => $this->get_option_value('overlay_opacity')
 
 
 
 
 
 
 
279
  );
280
- $lb_obj = array();
281
- foreach ($options as $option => $val) {
282
- if ($val === TRUE || $val == 'on')
283
- $val = 'true';
284
- elseif ($val === FALSE || empty($val))
285
- $val = 'false';
286
- $lb_obj[] = "'{$option}':{$val}";
287
- }
288
  //Load UI Strings
289
- if ( ($strings = $this->build_strings()) && !empty($strings) )
290
- $lb_obj[] = $strings;
291
- $js_code[] = 'Lightbox.initialize({' . implode(',', $lb_obj) . '});';
292
- echo $out['script_start'] . implode('', $js_code) . $out['script_end'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
 
295
  /**
296
  * Build JS object of UI strings when initializing lightbox
297
- * @return string JS object of UI strings
298
  */
299
- function build_strings() {
300
- $ret = '';
 
301
  $prefix = 'txt_';
302
- $opt_strings = array_filter(array_keys($this->options_default), create_function('$opt', 'return ( strpos($opt, "' . $prefix . '") === 0 );'));
303
- if ( $opt_strings ) {
304
- $strings = array();
305
  foreach ( $opt_strings as $key ) {
306
  $name = substr($key, strlen($prefix));
307
- $strings[] = "'" . $name . "':'" . $this->get_option_value($key) . "'";
308
  }
309
- $ret = "'strings':{" . implode(',', $strings) . "}";
310
  }
311
  return $ret;
312
  }
@@ -315,6 +1299,7 @@ class SLB_Lightbox extends SLB_Base {
315
 
316
  /**
317
  * Adds custom links below plugin on plugin listing page
 
318
  * @param $actions
319
  * @param $plugin_file
320
  * @param $plugin_data
@@ -323,9 +1308,9 @@ class SLB_Lightbox extends SLB_Base {
323
  function admin_plugin_action_links($actions, $plugin_file, $plugin_data, $context) {
324
  //Add link to settings (only if active)
325
  if ( is_plugin_active($this->util->get_plugin_base_name()) ) {
326
- $settings = __('Settings');
327
- $reset = __('Reset');
328
- $reset_confirm = "'" . __('Are you sure you want to reset your settings?') . "'";
329
  $action = $this->add_prefix('reset');
330
  $reset_link = wp_nonce_url(add_query_arg('action', $action, remove_query_arg(array($this->add_prefix('action'), 'action'), $_SERVER['REQUEST_URI'])), $action);
331
  array_unshift($actions, '<a class="delete" href="options-media.php#' . $this->admin_get_settings_section() . '" title="' . $settings . '">' . $settings . '</a>');
@@ -334,6 +1319,52 @@ class SLB_Lightbox extends SLB_Base {
334
  return $actions;
335
  }
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  /**
338
  * Reset plugin settings
339
  * Redirects to referring page upon completion
@@ -341,11 +1372,11 @@ class SLB_Lightbox extends SLB_Base {
341
  function admin_reset() {
342
  //Validate user
343
  if ( ! current_user_can('activate_plugins') || ! check_admin_referer($this->add_prefix('reset')) )
344
- wp_die(__('You do not have sufficient permissions to manage plugins for this blog.'));
345
  $action = 'reset';
346
  if ( isset($_REQUEST['action']) && $this->add_prefix($action) == $_REQUEST['action'] ) {
347
  //Reset settings
348
- $this->reset_options(true);
349
  $uri = remove_query_arg(array('_wpnonce', 'action'), add_query_arg(array($this->add_prefix('action') => $action), $_SERVER['REQUEST_URI']));
350
  //Redirect user
351
  wp_redirect($uri);
@@ -378,6 +1409,7 @@ class SLB_Lightbox extends SLB_Base {
378
  /**
379
  * Adds settings section for Lightbox functionality
380
  * Section is added to Settings > Media Admin menu
 
381
  */
382
  function admin_settings() {
383
  $page = $this->options_admin_page;
@@ -390,37 +1422,26 @@ class SLB_Lightbox extends SLB_Base {
390
  $page = 'media';
391
  $section = $this->get_prefix();
392
  //Section
393
- add_settings_section($section, '<span id="' . $this->admin_get_settings_section() . '">' . __('Lightbox Settings') . '</span>', $this->m('admin_section'), $page);
394
- //Fields
395
- foreach ($this->options_default as $key => $defaults) {
396
- $id = $this->add_prefix($key);
397
- $func = 'admin_field_' . $key;
398
- $label = ( isset($defaults[1]) ) ? $defaults[1] : '';
399
- $callback = ( method_exists($this, $func) ) ? $this->m($func) : $this->m('admin_field_default');
400
- $args = array('opt' => $key);
401
- //Check if option is a section header
402
- if ( ! is_array($defaults) ) {
403
- $label = '<h4 class="subhead">' . $defaults . '</h4>';
404
- $callback = $this->m('admin_field_header');
405
- } elseif ( is_null(get_option($id, null)) ) {
406
- //Add option to DB if not yet set
407
- $args['label_for'] = $id;
408
- update_option($id, htmlentities2($defaults[0]));
409
- }
410
- add_settings_field($id, __($label), $callback, $page, $section, $args);
411
- register_setting($page, $id);
412
- }
413
- }
414
 
 
 
 
 
415
  function admin_enqueue_files() {
416
- if ( is_admin() && basename($_SERVER['SCRIPT_NAME']) == $this->options_admin_page ) {
417
- wp_enqueue_style($this->add_prefix('admin_styles'), $this->util->get_file_url('css/admin.css'));
 
418
  }
419
  }
420
 
421
  /**
422
  * Get ID of settings section on admin page
423
  * @return string ID of settings section
 
424
  */
425
  function admin_get_settings_section() {
426
  return $this->add_prefix('settings');
@@ -429,67 +1450,34 @@ class SLB_Lightbox extends SLB_Base {
429
  /**
430
  * Placeholder function for lightbox admin settings
431
  * Required because setting init function requires a callback
 
432
  */
433
- function admin_section() { }
434
-
435
- /**
436
- * General field builder
437
- * @param string $option Option name to build field for
438
- * @param string $format Field markup (using sprintf specifiers)
439
- * @param string $type (optional) Type of field being build (e.g. checkbox, text, etc.)
440
- * Specifiers:
441
- * 1. Field ID
442
- * 2. Field Value
443
- * 3. Field Default Value (formatted)
444
- * 4. Field Type
445
- */
446
- function admin_the_field($option, $format = '', $type = '') {
447
- $opt = $this->get_option($option);
448
- $format_default = '<input id="%1$s" name="%1$s" %4$s class="code" /> (Default: %3$s)';
449
- if ( empty($format) && $format !== false )
450
- $format = $format_default;
451
- if ( empty($type) || !is_string($type) ) {
452
- $type_default = 'text';
453
- $type = ( is_bool($opt->value_default) ) ? 'checkbox' : $type_default;
454
- }
455
- //Adjust type and value formatting based on type
456
- switch ( $type ) {
457
- case 'checkbox' :
458
- if ( $opt->value )
459
- $opt->attr['checked'] = 'checked';
460
- break;
461
- case 'text' :
462
- if ( $format == $format_default )
463
- $format = str_replace('%4$s', '%4$s value="%2$s"', $format);
464
- break;
465
- }
466
- $opt->attr['type'] = $type;
467
- //Build attribute string
468
- $attr = '';
469
- if ( ! empty($opt->attr) ) {
470
- $attr = $this->util->build_attribute_string($opt->attr);
471
- }
472
-
473
- echo sprintf($format, $opt->id, htmlentities($opt->value), $opt->value_default_formatted, $attr);
474
  }
475
 
476
- /**
477
- * Builds header for settings subsection
478
- * @param array $args Arguments set in admin_settings
479
- */
480
- function admin_field_header($args) {
481
- $opt = ( isset($args['opt']) ) ? $args['opt'] : '';
482
- $this->admin_the_field($opt, false, 'header');
483
- }
484
 
485
- /**
486
- * Default field output generator
487
- * @param array $args Arguments set in admin_settings
488
- */
489
- function admin_field_default($args = array()) {
490
- $opt = ( isset($args['opt']) ) ? $args['opt'] : '';
491
- $this->admin_the_field($opt);
 
 
 
 
 
 
 
 
 
 
 
 
492
  }
493
  }
494
 
495
- ?>
1
  <?php
2
 
3
  require_once 'includes/class.base.php';
4
+ require_once 'includes/class.options.php';
5
 
6
  /**
7
  * Lightbox functionality class
12
 
13
  /*-** Properties **-*/
14
 
15
+ /**
16
+ * Themes
17
+ * @var array
18
+ */
19
+ var $themes = array();
20
+
21
+ var $theme_default = 'default';
22
+
23
  /**
24
  * Page that plugin options are on
25
  * @var string
33
  var $options_admin_form = 'options.php';
34
 
35
  /**
36
+ * Value to identify activated links
37
+ * Formatted on initialization
38
+ * @var string
39
+ */
40
+ var $attr = null;
41
+
42
+ /**
43
+ * Legacy attribute (for backwards compatibility)
44
+ * @var string
45
+ */
46
+ var $attr_legacy = 'lightbox';
47
+
48
+ /**
49
+ * Properties for media attachments in current request
50
+ * > Key (string) Attachment URI
51
+ * > Value (assoc-array) Attachment properties (url, etc.)
52
+ * > source: Source URL
53
+ * @var array
54
+ */
55
+ var $media_attachments = array();
56
+
57
+ /**
58
+ * Raw media items
59
+ * Used for populating media object on client-side
60
+ * > Key: Item URI
61
+ * > Value: Associative array of media properties
62
+ * > type: Item type (Default: null)
63
+ * > id: Item ID (Default: null)
64
  * @var array
65
  */
66
+ var $media_items_raw = array();
67
+
68
+ /**
69
+ * Media types
70
+ * @var array
71
+ */
72
+ var $media_types = array('img' => 'image', 'att' => 'attachment');
73
+
74
+ /* Widget properties */
75
+
76
+ /**
77
+ * Widget callback key
78
+ * @var string
79
+ */
80
+ var $widget_callback = 'callback';
81
+
82
+ /**
83
+ * Key to use to store original callback
84
+ * @var string
85
+ */
86
+ var $widget_callback_orig = 'callback_orig';
87
+
88
+ /**
89
+ * Used to track if widget is currently being processed or not
90
+ * @var bool
91
+ */
92
+ var $widget_processing = false;
93
+
94
+ /* Instance members */
95
+
96
+ /**
97
+ * Options instance
98
+ * @var SLB_Options
99
+ */
100
+ var $options = null;
101
+
102
+ /**
103
+ * Base field definitions
104
+ * Stores system/user-defined field definitions
105
+ * @var SLB_Fields
106
+ */
107
+ var $fields = null;
108
+
109
+ var $h_temp = array();
110
+
111
+ /* Constructor */
112
 
113
  function SLB_Lightbox() {
114
  $this->__construct();
117
  function __construct() {
118
  parent::__construct();
119
  $this->init();
120
+
121
+ //Init objects
122
+ $this->attr = $this->get_prefix();
123
+ $this->fields = new SLB_Fields();
124
  }
125
 
126
+ /* Init */
127
+
128
+ function init_env() {
129
+ //Localization
130
+ $ldir = 'l10n';
131
+ $lpath = $this->util->get_plugin_file_path($ldir, array(false, false));
132
+ $lpath_abs = $this->util->get_file_path($ldir);
133
+ if ( is_dir($lpath_abs) ) {
134
+ load_plugin_textdomain($this->util->get_plugin_textdomain(), false, $lpath);
135
+ }
136
+ //Options
137
+ $func_opts = 'init_options';
138
+ if ( isset($this) && method_exists($this, $func_opts) ) {
139
+ call_user_func($this->m($func_opts));
140
+ }
141
+
142
+ //Context
143
+ $func_context = $this->m('set_client_context');
144
+ $hook_context = ( is_admin() ) ? 'admin_head' : 'wp_head';
145
+ add_action($hook_context, $func_context);
146
+ }
147
+
148
+ /**
149
+ * Init options
150
+ */
151
+ function init_options() {
152
+ //Setup options
153
+ $p = $this->util->get_plugin_base(true);
154
+ $options_config = array (
155
+ 'groups' => array (
156
+ 'activation' => __('Activation', $p),
157
+ 'grouping' => __('Grouping', $p),
158
+ 'ui' => __('UI', $p),
159
+ 'labels' => __('Labels', $p)
160
+ ),
161
+ 'items' => array (
162
+ 'enabled' => array('title' => __('Enable Lightbox Functionality', $p), 'default' => true, 'group' => 'activation'),
163
+ 'enabled_home' => array('title' => __('Enable on Home page', $p), 'default' => true, 'group' => 'activation'),
164
+ 'enabled_post' => array('title' => __('Enable on Posts', $p), 'default' => true, 'group' => 'activation'),
165
+ 'enabled_page' => array('title' => __('Enable on Pages', $p), 'default' => true, 'group' => 'activation'),
166
+ 'enabled_archive' => array('title' => __('Enable on Archive Pages (tags, categories, etc.)', $p), 'default' => true, 'group' => 'activation'),
167
+ 'enabled_widget' => array('title' => __('Enable for Widgets', $p), 'default' => false, 'group' => 'activation'),
168
+ 'enabled_compat' => array('title' => __('Enable backwards-compatibility with legacy lightbox links', $p), 'default' => false, 'group' => 'activation'),
169
+ 'activate_attachments' => array('title' => __('Activate image attachment links', $p), 'default' => true, 'group' => 'activation'),
170
+ 'validate_links' => array('title' => __('Validate links', $p), 'default' => false, 'group' => 'activation'),
171
+ 'group_links' => array('title' => __('Group image links (for displaying as a slideshow)', $p), 'default' => true, 'group' => 'grouping'),
172
+ 'group_post' => array('title' => __('Group image links by Post (e.g. on pages with multiple posts)', $p), 'default' => true, 'group' => 'grouping'),
173
+ 'group_gallery' => array('title' => __('Group gallery links separately', $p), 'default' => false, 'group' => 'grouping'),
174
+ 'group_widget' => array('title' => __('Group widget links separately', $p), 'default' => false, 'group' => 'grouping'),
175
+ 'theme' => array('title' => __('Theme', $p), 'default' => 'default', 'group' => 'ui', 'parent' => 'option_theme'),
176
+ 'animate' => array('title' => __('Animate lightbox resizing', $p), 'default' => true, 'group' => 'ui'),
177
+ 'autostart' => array('title' => __('Automatically Start Slideshow', $p), 'default' => true, 'group' => 'ui'),
178
+ 'duration' => array('title' => __('Slide Duration (Seconds)', $p), 'default' => '6', 'attr' => array('size' => 3, 'maxlength' => 3), 'group' => 'ui'),
179
+ 'loop' => array('title' => __('Loop through images', $p), 'default' => true, 'group' => 'ui'),
180
+ 'overlay_opacity' => array('title' => __('Overlay Opacity (0 - 1)', $p), 'default' => '0.8', 'attr' => array('size' => 3, 'maxlength' => 3), 'group' => 'ui'),
181
+ 'enabled_caption' => array('title' => __('Enable caption', $p), 'default' => true, 'group' => 'ui'),
182
+ 'caption_src' => array('title' => __('Use image URI as caption when link title not set', $p), 'default' => true, 'group' => 'ui'),
183
+ 'enabled_desc' => array('title' => __('Enable description', $p), 'default' => true, 'group' => 'ui'),
184
+ 'txt_closeLink' => array('title' => __('Close link (for accessibility only, image used for button)', $p), 'default' => 'close', 'group' => 'labels'),
185
+ 'txt_loadingMsg' => array('title' => __('Loading indicator', $p), 'default' => 'loading', 'group' => 'labels'),
186
+ 'txt_nextLink' => array('title' => __('Next Image link', $p), 'default' => 'next &raquo;', 'group' => 'labels'),
187
+ 'txt_prevLink' => array('title' => __('Previous Image link', $p), 'default' => '&laquo; prev', 'group' => 'labels'),
188
+ 'txt_startSlideshow' => array('title' => __('Start Slideshow link', $p), 'default' => 'start slideshow', 'group' => 'labels'),
189
+ 'txt_stopSlideshow' => array('title' => __('Stop Slideshow link', $p), 'default' => 'stop slideshow', 'group' => 'labels'),
190
+ 'txt_numDisplayPrefix' => array('title' => __('Image number prefix (e.g. <strong>Image</strong> x of y)', $p), 'default' => 'Image', 'group' => 'labels'),
191
+ 'txt_numDisplaySeparator' => array('title' => __('Image number separator (e.g. Image x <strong>of</strong> y)', $p), 'default' => 'of', 'group' => 'labels')
192
+ ),
193
+ 'legacy' => array (
194
+ 'header_activation' => null,
195
+ 'header_enabled' => null,
196
+ 'header_strings' => null,
197
+ 'header_ui' => null,
198
+ 'enabled_single' => array('enabled_post', 'enabled_page')
199
+ )
200
+ );
201
+ $opt_theme =& $options_config['items']['theme'];
202
+ $opt_theme['default'] = $this->theme_default = $this->add_prefix($this->theme_default);
203
+ $opt_theme['options'] = $this->m('get_theme_options');
204
+
205
+ $this->options = new SLB_Options($options_config);
206
  }
207
 
208
  function register_hooks() {
209
+ parent::register_hooks();
210
+
211
  /* Admin */
212
+
213
  //Init lightbox admin
214
  add_action('admin_init', $this->m('admin_settings'));
215
  //Enqueue header files (CSS/JS)
217
  //Reset Settings
218
  add_action('admin_action_' . $this->add_prefix('reset'), $this->m('admin_reset'));
219
  add_action('admin_notices', $this->m('admin_notices'));
220
+ //Plugin listing
221
+ add_filter('plugin_action_links_' . $this->util->get_plugin_base_name(), $this->m('admin_plugin_action_links'), 10, 4);
222
+ add_action('in_plugin_update_message-' . $this->util->get_plugin_base_name(), $this->m('admin_plugin_update_message'), 10, 2);
223
+ add_filter('site_transient_update_plugins', $this->m('admin_plugin_update_transient'));
224
 
225
  /* Client-side */
226
+
227
+ //Init lightbox
228
+ $priority = 99;
229
  add_action('wp_enqueue_scripts', $this->m('enqueue_files'));
230
  add_action('wp_head', $this->m('client_init'));
231
+ add_action('wp_footer', $this->m('client_footer'), $priority);
232
+ //Link activation
233
+ add_filter('the_content', $this->m('activate_links'), $priority);
234
+ //Gallery wrapping
235
+ add_filter('the_content', $this->m('gallery_wrap'), 1);
236
+ add_filter('the_content', $this->m('gallery_unwrap'), $priority + 1);
237
+
238
+
239
+ /* Themes */
240
+ $this->util->add_action('init_themes', $this->m('init_default_themes'));
241
+
242
+ /* Widgets */
243
+ add_filter('sidebars_widgets', $this->m('sidebars_widgets'));
244
  }
245
+
246
+ /* Methods */
247
 
248
+ /*-** Request **-*/
249
+
250
  /**
251
+ * Output current context to client-side
252
+ * @uses `wp_head` action as hook
253
+ * @uses `admin_head` action as hook
254
+ * @return void
255
  */
256
+ function set_client_context() {
257
+ if ( !$this->is_enabled() )
258
+ return false;
259
+ $ctx = new stdClass();
260
+ $ctx->context = $this->util->get_context();
261
+ echo $this->util->build_script_element($this->util->extend_client_object($ctx), 'context');
 
 
262
  }
263
+
264
  /*-** Helpers **-*/
265
 
266
  /**
268
  * @return bool TRUE if lightbox is currently enabled, FALSE otherwise
269
  */
270
  function is_enabled($check_request = true) {
271
+ $ret = ( $this->options->get_bool('enabled') && !is_feed() && !is_admin() ) ? true : false;
272
  if ( $ret && $check_request ) {
273
  $opt = '';
274
  //Determine option to check
275
  if ( is_home() )
276
  $opt = 'home';
277
+ elseif ( is_singular() ) {
278
+ $opt = ( is_page() ) ? 'page' : 'post';
279
+ }
280
  elseif ( is_archive() || is_search() )
281
  $opt = 'archive';
282
  //Check option
283
+ if ( !empty($opt) && ( $opt = 'enabled_' . $opt ) && $this->options->has($opt) ) {
284
+ $ret = $this->options->get_bool($opt);
285
  }
286
  }
287
  return $ret;
288
  }
289
 
290
+ /*-** Widgets **-*/
291
+
292
  /**
293
+ * Reroute widget display handlers to internal method
294
+ * @param array $sidebar_widgets List of sidebars & their widgets
295
+ * @uses WP Hook `sidebars_widgets` to intercept widget list
296
+ * @global $wp_registered_widgets to reroute display callback
297
+ * @return array Sidebars and widgets (unmodified)
298
+ */
299
+ function sidebars_widgets($sidebars_widgets) {
300
+ global $wp_registered_widgets;
301
+ static $widgets_processed = false;
302
+ if ( is_admin() || empty($wp_registered_widgets) || $widgets_processed || !is_object($this->options) || !$this->is_enabled() || !$this->options->get_bool('enabled_widget') )
303
+ return $sidebars_widgets;
304
+ $widgets_processed = true;
305
+ //Fetch active widgets from all sidebars
306
+ foreach ( $sidebars_widgets as $sb => $ws ) {
307
+ //Skip inactive widgets and empty sidebars
308
+ if ( 'wp_inactive_widgets' == $sb || empty($ws) || !is_array($ws) )
309
+ continue;
310
+ foreach ( $ws as $w ) {
311
+ if ( isset($wp_registered_widgets[$w]) && isset($wp_registered_widgets[$w][$this->widget_callback]) ) {
312
+ $wref =& $wp_registered_widgets[$w];
313
+ //Backup original callback
314
+ $wref[$this->widget_callback_orig] = $wref[$this->widget_callback];
315
+ //Reroute callback
316
+ $wref[$this->widget_callback] = $this->m('widget_callback');
317
+ unset($wref);
318
+ }
319
+ }
320
+ }
321
+
322
+ return $sidebars_widgets;
323
  }
324
 
325
  /**
326
+ * Widget display handler
327
+ * Original widget display handler is called inside of an output buffer & links in output are processed before sending to browser
328
+ * @param array $args Widget instance properties
329
+ * @param int (optional) $widget_args Additional widget args (usually the widget's instance number)
330
+ * @see WP_Widget::display_callback() for more information
331
+ * @see sidebars_widgets() for callback modification
332
+ * @global $wp_registered_widgets
333
+ * @uses widget_process_links() to Process links in widget content
334
+ * @return void
335
  */
336
+ function widget_callback($args, $widget_args = 1) {
337
+ global $wp_registered_widgets;
338
+ $wid = ( isset($args['widget_id']) ) ? $args['widget_id'] : false;
339
+ //Stop processing if widget data invalid
340
+ if ( !$wid || !isset($wp_registered_widgets[$wid]) || !($w =& $wp_registered_widgets[$wid]) || !isset($w['id']) || $wid != $w['id'] )
341
+ return false;
342
+ //Get original callback
343
+ if ( !isset($w[$this->widget_callback_orig]) || !($cb = $w[$this->widget_callback_orig]) || !is_callable($cb) )
344
+ return false;
345
+ $params = func_get_args();
346
+ $this->widget_processing = true;
347
+ //Start output buffer
348
+ ob_start();
349
+ //Call original callback
350
+ call_user_func_array($cb, $params);
351
+ //Flush output buffer
352
+ echo $this->widget_process_links(ob_get_clean(), $wid);
353
+ $this->widget_processing = false;
354
  }
355
 
356
  /**
357
+ * Process links in widget content
358
+ * @param string $content Widget content
359
+ * @return string Processed widget content
360
+ * @uses process_links() to process links
361
  */
362
+ function widget_process_links($content, $id) {
363
+ $id = ( $this->options->get_bool('group_widget') ) ? "widget_$id" : null;
364
+ return $this->process_links($content, $id);
365
+ }
366
+
367
+ /*-** Theme **-*/
368
+
369
+ /**
370
+ * Retrieve themes
371
+ * @uses do_action() Calls 'slb_init_themes' hook to allow plugins to register themes
372
+ * @uses $themes to return registered themes
373
+ * @return array Retrieved themes
374
+ */
375
+ function get_themes() {
376
+ static $fetched = false;
377
+ if ( !$fetched ) {
378
+ $this->themes = array();
379
+ $this->util->do_action('init_themes');
380
+ $fetched = true;
381
+ }
382
+ return $this->themes;
383
  }
384
 
385
  /**
386
+ * Retrieve theme
387
+ * @param string $name Name of theme to retrieve
388
+ * @uses theme_exists() to check for existence of theme
389
+ * @return array Theme data
390
  */
391
+ function get_theme($name = '') {
392
+ $name = strval($name);
393
+ //Default: Get current theme if no theme specified
394
+ if ( empty($name) ) {
395
+ $name = $this->options->get_value('theme');
 
 
 
 
 
 
 
 
 
396
  }
397
+ if ( !$this->theme_exists($name) )
398
+ $name = $this->theme_default;
399
+ return $this->themes[$name];
400
+ }
401
+
402
+ /**
403
+ * Retrieve specific of theme data
404
+ * @uses get_theme() to retrieve theme data
405
+ * @param string $name Theme name
406
+ * @param string $field Theme field to retrieve
407
+ * @return mixed Field data
408
+ */
409
+ function get_theme_data($name = '', $field) {
410
+ $theme = $this->get_theme($name);
411
+ return ( isset($theme[$field]) ) ? $theme[$field] : '';
412
+ }
413
+
414
+ /**
415
+ * Retrieve theme stylesheet URL
416
+ * @param string $name Theme name
417
+ * @uses get_theme_data() to retrieve theme data
418
+ * @return string Stylesheet URL
419
+ */
420
+ function get_theme_style($name = '') {
421
+ return $this->get_theme_data($name, 'stylesheet_url');
422
+ }
423
+
424
+ /**
425
+ * Retrieve theme layout
426
+ * @uses get_theme_data() to retrieve theme data
427
+ * @param string $name Theme name
428
+ * @param bool $filter (optional) Filter layout based on user preferences
429
+ * @return string Theme layout HTML
430
+ */
431
+ function get_theme_layout($name = '', $filter = true) {
432
+ $l = $this->get_theme_data($name, 'layout');
433
+ //Filter
434
+ if ( !$this->options->get_bool('enabled_caption') )
435
+ $l = str_replace($this->get_theme_placeholder('dataCaption'), '', $l);
436
+ if ( !$this->options->get_bool('enabled_desc') )
437
+ $l = str_replace($this->get_theme_placeholder('dataDescription'), '', $l);
438
+ return $l;
439
+ }
440
+
441
+ /**
442
+ * Check whether a theme exists
443
+ * @param string $name Theme to look for
444
+ * @uses get_themes() to intialize themes if not already performed
445
+ * @return bool TRUE if theme exists, FALSE otherwise
446
+ */
447
+ function theme_exists($name) {
448
+ $this->get_themes();
449
+ return ( isset($this->themes[trim(strval($name))]) );
450
+ }
451
+
452
+ /**
453
+ * Register lightbox theme
454
+ * @param string $name Unique theme name
455
+ * @param string $title Display name for theme
456
+ * @param string $stylesheet_url URL to stylesheet
457
+ * @param string $layout Layout HTML
458
+ * @uses $themes to store the registered theme
459
+ */
460
+ function register_theme($name, $title, $stylesheet_url, $layout) {
461
+ if ( !is_array($this->themes) ) {
462
+ $this->themes = array();
463
+ }
464
+
465
+ //Validate parameters
466
+ $name = trim(strval($name));
467
+ $title = trim(strval($title));
468
+ $stylesheet_url = trim(strval($stylesheet_url));
469
+ $layout = $this->format_theme_layout($layout);
470
+
471
+ $defaults = array(
472
+ 'name' => '',
473
+ 'title' => '',
474
+ 'stylesheet_url' => '',
475
+ 'layout' => ''
476
+ );
477
+
478
+ //Add theme to array
479
+ $this->themes[$name] = wp_parse_args(compact(array_keys($defaults), $defaults));
480
+ }
481
+
482
+ /**
483
+ * Build theme placeholder
484
+ * @param string $name Placeholder name
485
+ * @return string Placeholder
486
+ */
487
+ function get_theme_placeholder($name) {
488
+ return '{' . $name . '}';
489
+ }
490
+
491
+ /**
492
+ * Formats layout for usage in JS
493
+ * @param string $layout Layout to format
494
+ * @return string Formatted layout
495
+ */
496
+ function format_theme_layout($layout = '') {
497
+ //Remove line breaks
498
+ $layout = str_replace(array("\r\n", "\n", "\r", "\t"), '', $layout);
499
+
500
+ //Escape quotes
501
+ $layout = str_replace("'", "\'", $layout);
502
+
503
+ //Return
504
+ return "'" . $layout . "'";
505
+ }
506
+
507
+ /**
508
+ * Add default themes
509
+ * @uses register_theme() to register the theme(s)
510
+ */
511
+ function init_default_themes() {
512
+ $name = $this->theme_default;
513
+ $title = 'Default';
514
+ $stylesheet_url = $this->util->get_file_url('css/lightbox.css');
515
+ $layout = file_get_contents($this->util->normalize_path($this->util->get_path_base(), 'templates', 'default', 'layout.html'));
516
+ $this->register_theme($name, $title, $stylesheet_url, $layout);
517
+ //Testing: Additional themes
518
+ $this->register_theme('black', 'Black', $this->util->get_file_url('css/lb_black.css'), $layout);
519
  }
520
 
521
  /*-** Frontend **-*/
522
 
523
+ /**
524
+ * Builds wrapper for grouping
525
+ * @return object Wrapper properties
526
+ * > open
527
+ * > close
528
+ */
529
+ function group_get_wrapper() {
530
+ static $wrapper = null;
531
+ if ( is_null($wrapper) ) {
532
+ $start = '<';
533
+ $end = '>';
534
+ $terminate = '/';
535
+ $val = $this->add_prefix('group');
536
+ //Build properties
537
+ $wrapper = array(
538
+ 'open' => $start . $val . $end,
539
+ 'close' => $start . $terminate . $val . $end
540
+ );
541
+ //Convert to object
542
+ $wrapper = (object) $wrapper;
543
+ }
544
+ return $wrapper;
545
+ }
546
+
547
+ /**
548
+ * Wraps galleries for grouping
549
+ * @uses `the_content` Filter hook
550
+ * @uses gallery_wrap_callback to Wrap shortcodes for grouping
551
+ * @param string $content Post content
552
+ * @return string Modified post content
553
+ */
554
+ function gallery_wrap($content) {
555
+ if ( !$this->is_enabled() )
556
+ return $content;
557
+ //Stop processing if option not enabled
558
+ if ( !$this->options->get_bool('group_gallery') )
559
+ return $content;
560
+ global $shortcode_tags;
561
+ //Save default shortcode handlers to temp variable
562
+ $sc_temp = $shortcode_tags;
563
+ //Find gallery shortcodes
564
+ $shortcodes = array('gallery', 'nggallery');
565
+ $m = $this->m('gallery_wrap_callback');
566
+ $shortcode_tags = array();
567
+ foreach ( $shortcodes as $tag ) {
568
+ $shortcode_tags[$tag] = $m;
569
+ }
570
+ //Wrap gallery shortcodes
571
+ $content = do_shortcode($content);
572
+ //Restore default shortcode handlers
573
+ $shortcode_tags = $sc_temp;
574
+
575
+ return $content;
576
+ }
577
+
578
+ /**
579
+ * Wraps gallery shortcodes for later processing
580
+ * @param array $attr Shortcode attributes
581
+ * @param string $content Content enclosed in shortcode
582
+ * @param string $tag Shortcode name
583
+ * @return string Wrapped gallery shortcode
584
+ */
585
+ function gallery_wrap_callback($attr, $content = null, $tag) {
586
+ //Rebuild shortcode
587
+ $sc = '[' . $tag . ' ' . $this->util->build_attribute_string($attr) . ']';
588
+ if ( !empty($content) )
589
+ $sc .= $content . '[/' . $tag .']';
590
+ //Wrap shortcode
591
+ $w = $this->group_get_wrapper();
592
+ $sc = $w->open . $sc . $w->close;
593
+ return $sc;
594
+ }
595
+
596
+ /**
597
+ * Removes wrapping from galleries
598
+ * @uses `the_content` filter hook
599
+ * @param $content Post content
600
+ * @return string Modified post content
601
+ */
602
+ function gallery_unwrap($content) {
603
+ if ( !$this->is_enabled() )
604
+ return $content;
605
+ //Stop processing if option not enabled
606
+ if ( !$this->options->get_bool('group_gallery') )
607
+ return $content;
608
+ $w = $this->group_get_wrapper();
609
+ if ( strpos($content, $w->open) !== false ) {
610
+ $content = str_replace($w->open, '', $content);
611
+ $content = str_replace($w->close, '', $content);
612
+ }
613
+ return $content;
614
+ }
615
+
616
+ /**
617
+ * Retrieve supported media types
618
+ * @return object Supported media types
619
+ */
620
+ function get_media_types() {
621
+ static $t = null;
622
+ if ( is_null($t) )
623
+ $t = (object) $this->media_types;
624
+ return $t;
625
+ }
626
+
627
+ /**
628
+ * Check if media type is supported
629
+ * @param string $type Media type
630
+ * @return bool If media type is supported
631
+ */
632
+ function is_media_type_supported($type) {
633
+ $ret = false;
634
+ $t = $this->get_media_types();
635
+ foreach ( $t as $n => $v ) {
636
+ if ( $type == $v ) {
637
+ $ret = true;
638
+ break;
639
+ }
640
+ }
641
+ return $ret;
642
+ }
643
+
644
  /**
645
  * Scans post content for image links and activates them
646
  *
647
  * Lightbox will not be activated for feeds
648
  * @param $content
649
+ * @return string Post content
650
  */
651
+ function activate_links($content) {
652
+ //Activate links only if enabled
653
+ if ( !$this->is_enabled() ) {
654
+ return $content;
655
+ }
656
+
657
+ $groups = array();
658
+ $w = $this->group_get_wrapper();
659
+ $g_ph_f = '[%s]';
660
+
661
+ //Strip groups
662
+ if ( $this->options->get_bool('group_gallery') ) {
663
+ $groups = array();
664
+ static $g_idx = 1;
665
+ $g_end_idx = 0;
666
+ //Iterate through galleries
667
+ while ( ($g_start_idx = strpos($content, $w->open, $g_end_idx)) && $g_start_idx !== false
668
+ && ($g_end_idx = strpos($content, $w->close, $g_start_idx)) && $g_end_idx != false ) {
669
+ $g_start_idx += strlen($w->open);
670
+ //Extract gallery content & save for processing
671
+ $g_len = $g_end_idx - $g_start_idx;
672
+ $groups[$g_idx] = substr($content, $g_start_idx, $g_len);
673
+ //Replace content with placeholder
674
+ $g_ph = sprintf($g_ph_f, $g_idx);
675
+ $content = substr_replace($content, $g_ph, $g_start_idx, $g_len);
676
+ //Increment gallery count
677
+ $g_idx++;
678
+ //Update end index
679
+ $g_end_idx = $g_start_idx + strlen($w->open);
680
+ }
681
+ }
682
+
683
+ //General link processing
684
+ $content = $this->process_links($content);
685
+
686
+ //Reintegrate Groups
687
+ foreach ( $groups as $group => $g_content ) {
688
+ $g_ph = $w->open . sprintf($g_ph_f, $group) . $w->close;
689
+ //Skip group if placeholder does not exist in content
690
+ if ( strpos($content, $g_ph) === false ) {
691
+ continue;
692
+ }
693
+ //Replace placeholder with processed content
694
+ $content = str_replace($g_ph, $w->open . $this->process_links($g_content, 'gallery_' . $group) . $w->close, $content);
695
+ }
696
+ return $content;
697
+ }
698
+
699
+ /**
700
+ * Retrieve HTML links in content
701
+ * @param string $content Content to get links from
702
+ * @param bool (optional) $unique Remove duplicates from returned links (Default: FALSE)
703
+ * @return array Links in content
704
+ */
705
+ function get_links($content, $unique = false) {
706
+ $rgx = "/\<a[^\>]+href=.*?\>/i";
707
+ $links = array();
708
+ preg_match_all($rgx, $content, $links);
709
+ $links = $links[0];
710
+ if ( $unique )
711
+ $links = array_unique($links);
712
+ return $links;
713
+ }
714
+
715
+ /**
716
+ * Process links in content
717
+ * @global obj $wpdb DB instance
718
+ * @global obj $post Current post
719
+ * @param string $content Text containing links
720
+ * @param string (optional) $group Group to add links to (Default: none)
721
+ * @return string Content with processed links
722
+ */
723
+ function process_links($content, $group = null) {
724
+ //Validate content before processing
725
+ if ( !is_string($content) || empty($content) )
726
+ return $content;
727
+ $links = $this->get_links($content, true);
728
+ //Process links
729
+ if ( count($links) > 0 ) {
730
+ global $wpdb;
731
+ global $post;
732
+ $types = $this->get_media_types();
733
+ $img_types = array('jpg', 'jpeg', 'gif', 'png');
734
+ $protocol = array('http://', 'https://');
735
+ $domain = str_replace($protocol, '', strtolower(get_bloginfo('url')));
736
+
737
+ //Format Group
738
+ $group_base = ( is_scalar($group) ) ? trim(strval($group)) : '';
739
+ if ( !$this->options->get_bool('group_links') ) {
740
+ $group_base = null;
741
+ }
742
+
743
+ //Iterate through links & add lightbox if necessary
744
+ foreach ( $links as $link ) {
745
+ //Init vars
746
+ $pid = 0;
747
+ $link_new = $link;
748
+ $internal = false;
749
+ $group = $group_base;
750
+
751
+ //Parse link attributes
752
+ $attr = $this->util->parse_attribute_string($link_new, array('rel' => '', 'href' => ''));
753
+ $h =& $attr['href'];
754
+ $r =& $attr['rel'];
755
+ $attrs_all = $this->get_attributes($r, false);
756
+ $attrs = $this->get_attributes($attrs_all);
757
+
758
+ //Stop processing invalid, disabled, or legacy links
759
+ if ( empty($h)
760
+ || 0 === strpos($h, '#')
761
+ || $this->has_attribute($attrs, $this->make_attribute_disabled())
762
+ || $this->has_attribute($attrs_all, $this->attr_legacy, false)
763
+ )
764
+ continue;
765
+
766
+ //Check if item links to internal media (attachment)
767
+ $hdom = str_replace($protocol, '', strtolower($h));
768
+ if ( strpos($hdom, $domain) === 0 ) {
769
+ //Save URL for further processing
770
+ $internal = true;
771
+ }
772
+
773
+ //Determine link type
774
+ $type = false;
775
+
776
+ //Check if link has already been processed
777
+ if ( $internal && $this->media_item_cached($h) ) {
778
+ $i = $this->get_cached_media_item($h);
779
+ $type = $i['type'];
780
+ }
781
+
782
+ elseif ( $this->util->has_file_extension($h, $img_types) ) {
783
+ //Direct Image file
784
+ $type = $types->img;
785
+ }
786
+
787
+ elseif ( $internal && is_local_attachment($h) && ( $pid = url_to_postid($h) ) && wp_attachment_is_image($pid) ) {
788
+ //Attachment URI
789
+ $type = $types->att;
790
+ }
791
+
792
+ //Stop processing if link type not valid
793
+ if ( !$type || ( $type == $types->att && !$this->options->get_bool('activate_attachments') ) )
794
+ continue;
795
+
796
+ //Set group (if necessary)
797
+ if ( $this->options->get_bool('group_links') ) {
798
+ //Normalize group
799
+ if ( !is_string($group) )
800
+ $group = '';
801
+ //Get preset group attribute
802
+ $g_name = $this->make_attribute_name('group');
803
+ $g = ( $this->has_attribute($attrs, $g_name) ) ? $this->get_attribute($attrs, $g_name) : $this->get_attribute($attrs, $this->attr);
804
+
805
+ if ( is_string($g) && ($g = trim($g)) && strlen($g) )
806
+ $group = $g;
807
+ //Group links by post?
808
+ if ( !$this->widget_processing && $this->options->get_bool('group_post') ) {
809
+ $group = ( strlen($group) ) ? '_' . $group : '';
810
+ $group = $post->ID . $group;
811
  }
812
 
813
+ if ( empty($group) ) {
814
+ $group = $this->get_prefix();
 
 
 
 
815
  }
816
+
817
+ //Set group attribute
818
+ $attrs = $this->set_attribute($attrs, $g_name, $group);
819
  }
820
+
821
+ //Activate link
822
+ $attrs = $this->set_attribute($attrs, $this->attr);
823
+
824
+ //Process internal links
825
+ if ( $internal ) {
826
+ //Mark as internal
827
+ $attrs = $this->set_attribute($attrs, 'internal');
828
+ //Add to media items array
829
+ $this->cache_media_item($h, $type, $pid);
830
+ }
831
+
832
+ //Convert rel attribute to string
833
+ $r = $this->build_attributes(array_merge($attrs_all, $attrs));
834
+
835
+ //Update link in content
836
+ $link_new = '<a ' . $this->util->build_attribute_string($attr) . '>';
837
+ $content = str_replace($link, $link_new, $content);
838
+ unset($h, $r);
839
  }
840
  }
841
+ return $content;
842
+ }
843
+
844
+ /**
845
+ * Generates link attributes from array
846
+ * @param array $attrs Link Attributes
847
+ * @return string Attribute string
848
+ */
849
+ function build_attributes($attrs) {
850
+ $a = array();
851
+ //Validate attributes
852
+ $attrs = $this->get_attributes($attrs, false);
853
+ //Iterate through attributes and build output array
854
+ foreach ( $attrs as $key => $val ) {
855
+ //Standard attributes
856
+ if ( is_bool($val) && $val ) {
857
+ $a[] = $key;
858
+ }
859
+ //Attributes with values
860
+ elseif ( is_string($val) ) {
861
+ $a[] = $key . '[' . $val . ']';
862
+ }
863
+ }
864
+ return implode(' ', $a);
865
+ }
866
+
867
+ /**
868
+ * Build attribute name
869
+ * Makes sure name is only prefixed once
870
+ * @return string Formatted attribute name
871
+ */
872
+ function make_attribute_name($name = '') {
873
+ $sep = '_';
874
+ $name = trim($name);
875
+ //Generate valid name
876
+ if ( $name != $this->attr ) {
877
+ //Use default name
878
+ if ( empty($name) )
879
+ $name = $this->attr;
880
+ //Add prefix if not yet set
881
+ elseif ( strpos($name, $this->attr . $sep) !== 0 )
882
+ $name = $this->attr . $sep . $name;
883
+ }
884
+ return $name;
885
+ }
886
+
887
+ /**
888
+ * Create attribute to disable lightbox for current link
889
+ * @return string Disabled lightbox attribute
890
+ */
891
+ function make_attribute_disabled() {
892
+ static $ret = null;
893
+ if ( is_null($ret) ) {
894
+ $ret = $this->make_attribute_name('off');
895
+ }
896
+ return $ret;
897
+ }
898
+
899
+ /**
900
+ * Set attribute to array
901
+ * Attribute is added to array if it does not exist
902
+ * @param array $attrs Current attribute array
903
+ * @param string $name Name of attribute to add
904
+ * @param string (optional) $value Attribute value
905
+ * @return array Updated attribute array
906
+ */
907
+ function set_attribute($attrs, $name, $value = null) {
908
+ //Validate attribute array
909
+ $attrs = $this->get_attributes($attrs, false);
910
+ //Build attribute name
911
+ $name = $this->make_attribute_name($name);
912
+ //Set attribute
913
+ $attrs[$name] = true;
914
+ if ( !empty($value) && is_string($value) )
915
+ $attrs[$name] = $value;
916
+ return $attrs;
917
+ }
918
+
919
+ /**
920
+ * Convert attribute string into array
921
+ * @param string $attr_string Attribute string
922
+ * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
923
+ * @return array Attributes as associative array
924
+ */
925
+ function get_attributes($attr_string, $internal = true) {
926
+ $ret = array();
927
+ //Protect bracketed values prior to parsing attributes string
928
+ if ( is_string($attr_string) ) {
929
+ $attr_string = trim($attr_string);
930
+ $attr_vals = array();
931
+ $attr_keys = array();
932
+ $offset = 0;
933
+ while ( ($bo = strpos($attr_string,'[', $offset)) && $bo !== false
934
+ && ($bc = strpos($attr_string,']', $bo)) && $bc !== false
935
+ ) {
936
+ //Push all preceding attributes into array
937
+ $attr_temp = explode(' ', substr($attr_string, $offset, $bo));
938
+ //Get attribute name
939
+ $name = array_pop($attr_temp);
940
+ $attr_keys = array_merge($attr_keys, $attr_temp);
941
+ //Add to values array
942
+ $attr_vals[$name] = substr($attr_string, $bo+1, $bc-$bo-1);
943
+ //Update offset
944
+ $offset = $bc+1;
945
+ }
946
+ //Parse remaining attributes
947
+ $attr_keys = array_merge($attr_keys, array_filter(explode(' ', substr($attr_string, $offset))));
948
+ //Set default values for all keys
949
+ $attr_keys = array_fill_keys($attr_keys, TRUE);
950
+ //Merge attributes with values
951
+ $ret = array_merge($attr_keys, $attr_vals);
952
+ } elseif ( is_array($attr_string) )
953
+ $ret = $attr_string;
954
+
955
+ //Filter non-internal attributes if necessary
956
+ if ( $internal && is_array($ret) ) {
957
+ foreach ( array_keys($ret) as $attr ) {
958
+ if ( $attr == $this->attr)
959
+ continue;
960
+ if ( strpos($attr, $this->attr . '_') !== 0 )
961
+ unset($ret[$attr]);
962
+ }
963
+ }
964
+
965
+ return $ret;
966
+ }
967
+
968
+ /**
969
+ * Retrieve attribute value
970
+ * @param string|array $attrs Attributes to retrieve attribute value from
971
+ * @param string $attr Attribute name to retrieve
972
+ * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
973
+ * @return string|bool Attribute value (Default: FALSE)
974
+ */
975
+ function get_attribute($attrs, $attr, $internal = true) {
976
+ $ret = false;
977
+ $attrs = $this->get_attributes($attrs, $internal);
978
+ //Validate attribute name for internal attributes
979
+ if ( $internal )
980
+ $attr = $this->make_attribute_name($attr);
981
+ if ( isset($attrs[$attr]) ) {
982
+ $ret = $attrs[$attr];
983
+ }
984
+ return $ret;
985
+ }
986
+
987
+ /**
988
+ * Checks if attribute exists
989
+ * @param string|array $attrs Attributes to retrieve attribute value from
990
+ * @param string $attr Attribute name to retrieve
991
+ * @param bool (optional) $internal Whether only internal attributes should be evaluated (Default: TRUE)
992
+ * @return bool Whether or not attribute exists
993
+ */
994
+ function has_attribute($attrs, $attr, $internal = true) {
995
+ return ( $this->get_attribute($attrs, $attr, $internal) !== false ) ? true : false;
996
+ }
997
+
998
+ /**
999
+ * Cache media properties for later processing
1000
+ * @param string $uri URI for internal media (e.g. direct uri, attachment uri, etc.)
1001
+ * @param string $type Media type (image, attachment, etc.)
1002
+ * @param int (optional) $id ID of media item (if available) (Default: NULL)
1003
+ */
1004
+ function cache_media_item($uri, $type, $id = null) {
1005
+ //Cache media item
1006
+ if ( $this->is_media_type_supported($type) && !$this->media_item_cached($uri) ) {
1007
+ //Set properties
1008
+ $i = array('type' => null, 'id' => null, 'source' => null);
1009
+ //Type
1010
+ $i['type'] = $type;
1011
+ $t = $this->get_media_types();
1012
+ //Source
1013
+ if ( $type == $t->img )
1014
+ $i['source'] = $uri;
1015
+ //ID
1016
+ if ( is_numeric($id) )
1017
+ $i['id'] = absint($id);
1018
+
1019
+ $this->media_items_raw[$uri] = $i;
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Checks if media item has already been cached
1025
+ * @param string $uri URI of media item
1026
+ * @return boolean Whether media item has been cached
1027
+ */
1028
+ function media_item_cached($uri) {
1029
+ $ret = false;
1030
+ if ( !$uri || !is_string($uri) )
1031
+ return $ret;
1032
+ return ( isset($this->media_items_raw[$uri]) ) ? true : false;
1033
+ }
1034
+
1035
+ /**
1036
+ * Retrieve cached media item
1037
+ * @param string $uri Media item URI
1038
+ * @return array|null Media item properties (NULL if not set)
1039
+ */
1040
+ function get_cached_media_item($uri) {
1041
+ $ret = null;
1042
+ if ( $this->media_item_cached($uri) ) {
1043
+ $ret = $this->media_items_raw[$uri];
1044
+ }
1045
+ return $ret;
1046
+ }
1047
+
1048
+ /**
1049
+ * Retrieve cached media items
1050
+ * @return array Cached media items
1051
+ */
1052
+ function &get_cached_media_items() {
1053
+ return $this->media_items_raw;
1054
+ }
1055
+
1056
+ /**
1057
+ * Check if media items have been cached
1058
+ * @return boolean
1059
+ */
1060
+ function has_cached_media_items() {
1061
+ return ( !empty($this->media_items_raw) ) ? true : false;
1062
  }
1063
 
1064
  /**
1067
  function enqueue_files() {
1068
  if ( ! $this->is_enabled() )
1069
  return;
1070
+
1071
+ //$lib = 'js/' . ( ( ( defined('WP_DEBUG') && WP_DEBUG ) || isset($_REQUEST[$this->add_prefix('debug')]) ) ? 'dev/lib.dev.js' : 'lib.js' );
1072
+ $lib = 'js/lib.js';
1073
+ wp_enqueue_script($this->add_prefix('lib'), $this->util->get_file_url($lib), array('jquery'), $this->util->get_plugin_version());
1074
+ wp_enqueue_style($this->add_prefix('style'), $this->get_theme_style(), array(), $this->util->get_plugin_version());
1075
  }
1076
 
1077
  /**
1081
  function client_init() {
1082
  if ( ! $this->is_enabled() )
1083
  return;
1084
+ echo '<!-- SLB -->' . PHP_EOL;
1085
  $options = array();
1086
  $out = array();
 
 
1087
  $js_code = array();
 
 
 
 
 
 
 
 
 
1088
  //Get options
1089
  $options = array(
1090
+ 'validateLinks' => $this->options->get_bool('validate_links'),
1091
+ 'autoPlay' => $this->options->get_bool('autostart'),
1092
+ 'slideTime' => $this->options->get_value('duration'),
1093
+ 'loop' => $this->options->get_bool('loop'),
1094
+ 'overlayOpacity' => $this->options->get_value('overlay_opacity'),
1095
+ 'animate' => $this->options->get_bool('animate'),
1096
+ 'captionEnabled' => $this->options->get_bool('enabled_caption'),
1097
+ 'captionSrc' => $this->options->get_bool('caption_src'),
1098
+ 'descEnabled' => $this->options->get_bool('enabled_desc'),
1099
+ 'trigger' => array($this->get_prefix()),
1100
+ 'prefix' => $this->get_prefix()
1101
  );
1102
+ //Backwards compatibility
1103
+ if ( $this->options->get_bool('enabled_compat'))
1104
+ $options['trigger'][] = $this->attr_legacy;
1105
+
 
 
 
 
1106
  //Load UI Strings
1107
+ if ( ($strings = $this->build_labels()) && !empty($strings) )
1108
+ $options['labels'] = $strings;
1109
+ //Load Layout
1110
+ $options['layout'] = $this->get_theme_layout();
1111
+
1112
+ //Build client output
1113
+ echo $this->util->build_script_element($this->util->call_client_method('init', $options), 'init', true, true);
1114
+ echo PHP_EOL . '<!-- /SLB -->' . PHP_EOL;
1115
+ }
1116
+
1117
+ /**
1118
+ * Output code in footer
1119
+ * > Media attachment URLs
1120
+ * @uses `_wp_attached_file` to match attachment ID to URI
1121
+ * @uses `_wp_attachment_metadata` to retrieve attachment metadata
1122
+ */
1123
+ function client_footer() {
1124
+ echo '<!-- X -->';
1125
+ //Stop if not enabled or if there are no media items to process
1126
+ if ( !$this->is_enabled() || !$this->has_cached_media_items() )
1127
+ return;
1128
+ echo '<!-- SLB -->' . PHP_EOL;
1129
+
1130
+ global $wpdb;
1131
+
1132
+ $this->media_attachments = array();
1133
+ $props = array('id', 'type', 'desc', 'title', 'source');
1134
+ $props = (object) array_combine($props, $props);
1135
+
1136
+ //Separate media into buckets by type
1137
+ $m_bucket = array();
1138
+ $type = $id = null;
1139
+
1140
+ $m_items =& $this->get_cached_media_items();
1141
+ foreach ( $m_items as $uri => $p ) {
1142
+ $type = $p[$props->type];
1143
+ if ( empty($type) )
1144
+ continue;
1145
+ if ( !isset($m_bucket[$type]) )
1146
+ $m_bucket[$type] = array();
1147
+ //Add to bucket for type (by reference)
1148
+ $m_bucket[$type][$uri] =& $m_items[$uri];
1149
+ }
1150
+
1151
+ //Process links by type
1152
+ $t = $this->get_media_types();
1153
+
1154
+ //Direct image links
1155
+ if ( isset($m_bucket[$t->img]) ) {
1156
+ $b =& $m_bucket[$t->img];
1157
+ $uris_base = array();
1158
+ $uri_prefix = wp_upload_dir();
1159
+ $uri_prefix = $this->util->normalize_path($uri_prefix['baseurl'], true);
1160
+ foreach ( array_keys($b) as $uri ) {
1161
+ $uris_base[str_replace($uri_prefix, '', $uri)] = $uri;
1162
+ }
1163
+
1164
+ //Retrieve attachment IDs
1165
+ $uris_flat = "('" . implode("','", array_keys($uris_base)) . "')";
1166
+ $q = $wpdb->prepare("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE `meta_key` = %s AND LOWER(`meta_value`) IN $uris_flat LIMIT %d", '_wp_attached_file', count($b));
1167
+ $pids_temp = $wpdb->get_results($q);
1168
+ //Match IDs with URIs
1169
+ if ( $pids_temp ) {
1170
+ foreach ( $pids_temp as $pd ) {
1171
+ $f = $pd->meta_value;
1172
+ if ( is_numeric($pd->post_id) && isset($uris_base[$f]) ) {
1173
+ $b[$uris_base[$f]][$props->id] = absint($pd->post_id);
1174
+ }
1175
+ }
1176
+ }
1177
+ //Destroy worker vars
1178
+ unset($b, $uri, $uris_base, $uris_flat, $q, $pids_temp, $pd);
1179
+ }
1180
+
1181
+ //Image attachments
1182
+ if ( isset($m_bucket[$t->att]) ) {
1183
+ $b =& $m_bucket[$t->att];
1184
+
1185
+ //Attachment source URI
1186
+ foreach ( $b as $uri => $p ) {
1187
+ $s = wp_get_attachment_url($p[$props->id]);
1188
+ if ( !!$s )
1189
+ $b[$uri][$props->source] = $s;
1190
+ }
1191
+ //Destroy worker vars
1192
+ unset($b, $uri, $p);
1193
+ }
1194
+
1195
+ //Retrieve attachment IDs
1196
+ $ids = array();
1197
+ foreach ( $m_items as $uri => $p ) {
1198
+ //Add post ID to query
1199
+ if ( isset($p[$props->id]) ) {
1200
+ $id = $p[$props->id];
1201
+ //Create array for ID (support multiple URIs per ID)
1202
+ if ( !isset($ids[$id]) ) {
1203
+ $ids[$id] = array();
1204
+ }
1205
+ //Add URI to ID
1206
+ $ids[$id][] = $uri;
1207
+ }
1208
+ }
1209
+
1210
+ //Retrieve attachment properties
1211
+ if ( !empty($ids) ) {
1212
+ $ids_flat = array_keys($ids);
1213
+ $atts = get_posts(array('post_type' => 'attachment', 'include' => $ids_flat));
1214
+ $ids_flat = "('" . implode("','", $ids_flat) . "')";
1215
+ $atts_meta = $wpdb->get_results($wpdb->prepare("SELECT `post_id`,`meta_value` FROM $wpdb->postmeta WHERE `post_id` IN $ids_flat AND `meta_key` = %s LIMIT %d", '_wp_attachment_metadata', count($ids)));
1216
+ //Rebuild metadata array
1217
+ if ( $atts_meta ) {
1218
+ $meta = array();
1219
+ foreach ( $atts_meta as $att_meta ) {
1220
+ $meta[$att_meta->post_id] = $att_meta->meta_value;
1221
+ }
1222
+ $atts_meta = $meta;
1223
+ unset($meta);
1224
+ } else {
1225
+ $atts_meta = array();
1226
+ }
1227
+
1228
+ //Process attachments
1229
+ if ( $atts ) {
1230
+ foreach ( $atts as $att ) {
1231
+ if ( !isset($ids[$att->ID]) )
1232
+ continue;
1233
+ //Add attachment
1234
+ //Set properties
1235
+ $m = array(
1236
+ $props->title => $att->post_title,
1237
+ $props->desc => $att->post_content,
1238
+ );
1239
+ //Add metadata
1240
+ if ( isset($atts_meta[$att->ID]) && ($a = unserialize($atts_meta[$att->ID])) && is_array($a) ) {
1241
+ //Move original size into `sizes` array
1242
+ foreach ( array('file', 'width', 'height') as $d ) {
1243
+ if ( !isset($a[$d]) )
1244
+ continue;
1245
+ $a['sizes']['original'][$d] = $a[$d];
1246
+ unset($a[$d]);
1247
+ }
1248
+
1249
+ //Strip extraneous metadata
1250
+ foreach ( array('hwstring_small') as $d ) {
1251
+ if ( isset($a[$d]) )
1252
+ unset($a[$d]);
1253
+ }
1254
+
1255
+ $m = array_merge($a, $m);
1256
+ unset($a, $d);
1257
+ }
1258
+
1259
+ //Save to object
1260
+ foreach ( $ids[$att->ID] as $uri ) {
1261
+ if ( isset($m_items[$uri]) )
1262
+ $m = array_merge($m_items[$uri], $m);
1263
+ $this->media_attachments[$uri] = $m;
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+
1269
+ //Media attachments
1270
+ if ( !empty($this->media_attachments) ) {
1271
+ $obj = 'media';
1272
+ $atch_out = $this->util->extend_client_object($obj, $this->media_attachments);
1273
+ echo $this->util->build_script_element($atch_out, $obj);
1274
+ }
1275
+
1276
+ echo PHP_EOL . '<!-- /SLB -->' . PHP_EOL;
1277
  }
1278
 
1279
  /**
1280
  * Build JS object of UI strings when initializing lightbox
1281
+ * @return array UI strings
1282
  */
1283
+ function build_labels() {
1284
+ $ret = array();
1285
+ //Get all UI options
1286
  $prefix = 'txt_';
1287
+ $opt_strings = array_filter(array_keys($this->options->get_items()), create_function('$opt', 'return ( strpos($opt, "' . $prefix . '") === 0 );'));
1288
+ if ( count($opt_strings) ) {
1289
+ //Build array of UI options
1290
  foreach ( $opt_strings as $key ) {
1291
  $name = substr($key, strlen($prefix));
1292
+ $ret[$name] = $this->options->get_value($key);
1293
  }
 
1294
  }
1295
  return $ret;
1296
  }
1299
 
1300
  /**
1301
  * Adds custom links below plugin on plugin listing page
1302
+ * @uses `plugin_action_links_$plugin-name` Filter hook
1303
  * @param $actions
1304
  * @param $plugin_file
1305
  * @param $plugin_data
1308
  function admin_plugin_action_links($actions, $plugin_file, $plugin_data, $context) {
1309
  //Add link to settings (only if active)
1310
  if ( is_plugin_active($this->util->get_plugin_base_name()) ) {
1311
+ $settings = __('Settings', $this->get_prefix());
1312
+ $reset = __('Reset', $this->get_prefix());
1313
+ $reset_confirm = "'" . __('Are you sure you want to reset your settings?', $this->get_prefix()) . "'";
1314
  $action = $this->add_prefix('reset');
1315
  $reset_link = wp_nonce_url(add_query_arg('action', $action, remove_query_arg(array($this->add_prefix('action'), 'action'), $_SERVER['REQUEST_URI'])), $action);
1316
  array_unshift($actions, '<a class="delete" href="options-media.php#' . $this->admin_get_settings_section() . '" title="' . $settings . '">' . $settings . '</a>');
1319
  return $actions;
1320
  }
1321
 
1322
+ /**
1323
+ * Adds additional message for plugin updates
1324
+ * @uses `in_plugin_update_message-$plugin-name` Action hook
1325
+ * @var array $plugin_data Current plugin data
1326
+ * @var object $r Update response data
1327
+ */
1328
+ function admin_plugin_update_message($plugin_data, $r) {
1329
+ if ( !isset($r->new_version) )
1330
+ return false;
1331
+ if ( stripos($r->new_version, 'beta') !== false ) {
1332
+ $cls_notice = $this->add_prefix('notice');
1333
+ echo '<br />' . $this->admin_plugin_update_get_message($r);
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Modify update plugins response data if necessary
1339
+ * @uses `site_transient_update_plugins` Filter hook
1340
+ * @param obj $transient Transient data
1341
+ * @return obj Modified transient data
1342
+ */
1343
+ function admin_plugin_update_transient($transient) {
1344
+ $n = $this->util->get_plugin_base_name();
1345
+ if ( isset($transient->response) && isset($transient->response[$n]) && is_object($transient->response[$n]) && !isset($transient->response[$n]->upgrade_notice) ) {
1346
+ $r =& $transient->response[$n];
1347
+ $r->upgrade_notice = $this->admin_plugin_update_get_message($r);
1348
+ }
1349
+ return $transient;
1350
+ }
1351
+
1352
+ /**
1353
+ * Retrieve custom update message
1354
+ * @param obj $r Response data from plugin update API
1355
+ * @return string Message (Default: empty string)
1356
+ */
1357
+ function admin_plugin_update_get_message($r) {
1358
+ $msg = '';
1359
+ $cls_notice = $this->add_prefix('notice');
1360
+ if ( !is_object($r) || !isset($r->new_version) )
1361
+ return $msg;
1362
+ if ( stripos($r->new_version, 'beta') !== false ) {
1363
+ $msg = "<strong class=\"$cls_notice\">Notice:</strong> This update is a <strong class=\"$cls_notice\">Beta version</strong>. It is highly recommended that you test the update on a test server before updating the plugin on a production server.";
1364
+ }
1365
+ return $msg;
1366
+ }
1367
+
1368
  /**
1369
  * Reset plugin settings
1370
  * Redirects to referring page upon completion
1372
  function admin_reset() {
1373
  //Validate user
1374
  if ( ! current_user_can('activate_plugins') || ! check_admin_referer($this->add_prefix('reset')) )
1375
+ wp_die(__('You do not have sufficient permissions to manage plugins for this blog.', $this->get_prefix()));
1376
  $action = 'reset';
1377
  if ( isset($_REQUEST['action']) && $this->add_prefix($action) == $_REQUEST['action'] ) {
1378
  //Reset settings
1379
+ $this->options->reset(true);
1380
  $uri = remove_query_arg(array('_wpnonce', 'action'), add_query_arg(array($this->add_prefix('action') => $action), $_SERVER['REQUEST_URI']));
1381
  //Redirect user
1382
  wp_redirect($uri);
1409
  /**
1410
  * Adds settings section for Lightbox functionality
1411
  * Section is added to Settings > Media Admin menu
1412
+ * @todo Move appropriate code to options class
1413
  */
1414
  function admin_settings() {
1415
  $page = $this->options_admin_page;
1422
  $page = 'media';
1423
  $section = $this->get_prefix();
1424
  //Section
1425
+ add_settings_section($section, '<div id="' . $this->admin_get_settings_section() . '">' . __('Lightbox Settings', $this->util->get_plugin_textdomain()) . '</div>', $this->m('admin_section'), $page);
1426
+ //Register settings container
1427
+ register_setting($page, $this->add_prefix('options'), $this->options->m('validate'));
1428
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1429
 
1430
+ /**
1431
+ * Enqueues header files for admin pages
1432
+ * @todo Separate and move options CSS to options class
1433
+ */
1434
  function admin_enqueue_files() {
1435
+ //Enqueue custom CSS for options page
1436
+ if ( is_admin() && ( basename($_SERVER['SCRIPT_NAME']) == $this->options_admin_page || $this->util->is_context('admin_page_plugins')) ) {
1437
+ wp_enqueue_style($this->add_prefix('admin'), $this->util->get_file_url('css/admin.css'), array(), $this->util->get_plugin_version());
1438
  }
1439
  }
1440
 
1441
  /**
1442
  * Get ID of settings section on admin page
1443
  * @return string ID of settings section
1444
+ * @todo Eval for moving to options class
1445
  */
1446
  function admin_get_settings_section() {
1447
  return $this->add_prefix('settings');
1450
  /**
1451
  * Placeholder function for lightbox admin settings
1452
  * Required because setting init function requires a callback
1453
+ * @todo Evaluate for moving to options class
1454
  */
1455
+ function admin_section() {
1456
+ $this->options->build();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1457
  }
1458
 
1459
+ /* Custom fields */
 
 
 
 
 
 
 
1460
 
1461
+ function get_theme_options() {
1462
+ //Get themes
1463
+ $themes = $this->get_themes();
1464
+
1465
+ //Pop out default theme
1466
+ $theme_default = $themes[$this->theme_default];
1467
+ unset($themes[$this->theme_default]);
1468
+
1469
+ //Sort themes by title
1470
+ uasort($themes, create_function('$a,$b', 'return strcmp($a[\'title\'], $b[\'title\']);'));
1471
+
1472
+ //Insert default theme at top of array
1473
+ $themes = array($this->theme_default => $theme_default) + $themes;
1474
+
1475
+ //Build options
1476
+ foreach ( $themes as $name => $props ) {
1477
+ $themes[$name] = $props['title'];
1478
+ }
1479
+ return $themes;
1480
  }
1481
  }
1482
 
1483
+ ?>
readme.txt CHANGED
@@ -1,40 +1,50 @@
1
- === Plugin Name ===
2
- Contributors: archetyped
3
- Tags: lightbox, gallery, photography, images
4
- Requires at least: 2.9.2
5
- Tested up to: 3.0
 
 
6
  Stable tag: trunk
7
 
8
- A simple and customizable Lightbox for Wordpress
9
 
10
  == Description ==
11
 
12
- Simple Lightbox is a very simple and customizable lightbox that is easy to add to your Wordpress website
 
 
13
 
14
- #### Customization
 
 
15
  Options for customizing the lightbox behavior are located in the **Settings > Media** admin menu in the **Lightbox Settings** section (or just click the **Settings** link below the plugin's name when viewing the list of installed plugins)
16
 
17
- * Customizable UI Text
18
- * Enable/Disable Lightbox Functionality (Default: Enabled)
 
 
 
 
 
19
  * Enable Lightbox depending on Page Type (Home, Pages, Archive, etc.)
20
- * Automatically activate lightbox for links to images on page (no need to add `rel="lightbox"` attribute to link)
21
- * Group automatically-activated links (play as a slideshow)
22
- * Group image links by Post (separate slideshow for each Post on page)
23
- * Automatically Start Slideshow (Default: Enabled)
24
- * Slide Duration (Seconds) (Default: 6)
25
- * Loop through images (Default: Enabled)
26
- * Overlay Opacity (0 - 1) (Default: 0.8)
27
 
28
  #### Usage
29
- * The necessary Javascript and CSS files will be automatically added to the page as long as `wp_head()` is called in the theme
30
- * That's it!
 
 
 
31
 
32
  == Installation ==
33
 
34
- 1. Upload `simple-lightbox` folder to the `/wp-content/plugins/` directory
35
- 1. Activate the plugin through the 'Plugins' menu in WordPress
36
- 1. Verify that your theme uses the `wp_head()` template tag (this is where the necessary files will be added to your theme output)
37
- 1. Let plugin automatically add lightbox functionality for links to images or manually add `rel="lightbox"` to any image links that you want to be displayed in a lightbox
38
 
39
  == Upgrade Notice ==
40
 
@@ -42,7 +52,7 @@ No upgrade notices
42
 
43
  == Frequently Asked Questions ==
44
 
45
- Send your questions to wp@archetyped.com
46
 
47
  == Screenshots ==
48
 
@@ -50,13 +60,138 @@ Send your questions to wp@archetyped.com
50
  2. Customized UI Text
51
 
52
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  = 1.3.1 =
54
- * Updated: Utilities code (internal)
 
55
  = 1.3 =
56
- * Added: Customizable UI label text (close, next, and prev button images can be replaced in `images` directory)
57
- * Added: Group image links by Post (separate slideshow for each post)
58
- * Added: Reset settings link on plugin listings page
59
- * Optimized: Organized settings page
60
 
61
  = 1.2.1 =
62
  * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
@@ -72,5 +207,6 @@ Send your questions to wp@archetyped.com
72
  * Optimized: Options menu field building
73
  * Optimized: Loading of default values for plugin options
74
  * Optimized: General code optimizations
 
75
  = 1.0 =
76
  * Initial release
1
+ === Simple Lightbox ===
2
+ Contributors: Archetyped
3
+ Donate link: http://mycharitywater.org/archetyped-2012-fall
4
+ Tags: lightbox, gallery, photography, images, theme, template, style
5
+ Requires at least: 3.3.2
6
+ Tested up to: 3.4
7
+ License: GPLv2 or later
8
  Stable tag: trunk
9
 
10
+ A simple, themeable, and highly customizable Lightbox for Wordpress
11
 
12
  == Description ==
13
 
14
+ #### Simple Lightbox 2.0 Beta!
15
+ The next generation of Simple Lightbox is here and ready for brave beta testers.
16
+ [Sign up for SLB 2.0 beta now](http://archetyped.com/lab/simple-lightbox-2-0-beta/) while there's still space available!
17
 
18
+ Simple Lightbox is a very simple and highly customizable lightbox that is easy to add to your Wordpress website. It also [supports skins](http://archetyped.com/lab/slb-registering-themes/), so it can be fully integrated with your site's theme.
19
+
20
+ #### Features
21
  Options for customizing the lightbox behavior are located in the **Settings > Media** admin menu in the **Lightbox Settings** section (or just click the **Settings** link below the plugin's name when viewing the list of installed plugins)
22
 
23
+ * **Supports links in Widgets (New!)**
24
+ * **Supports Image Attachment links**
25
+ * **Display image metadata (caption, description, etc.) in lightbox**
26
+ * Automatically activate links (no manual coding required)
27
+ * Keyboard Navigation
28
+ * Link Validation (optional)
29
+ * User-customizable skin (template) support
30
  * Enable Lightbox depending on Page Type (Home, Pages, Archive, etc.)
31
+ * Group image links (play as a slideshow)
32
+ * Group image links by Post (e.g. separate slideshow for each post on home page)
33
+ * UI/Animation Customization
34
+ * Slideshow Customization
35
+ * Backwards-compatibility with legacy lightbox links
 
 
36
 
37
  #### Usage
38
+ 1. Insert links to images/image attachments into your posts/pages
39
+
40
+ **That's it! The image will be displayed in a lightbox automatically.**
41
+
42
+ * For more usage tips, go to [Simple Lightbox's official page](http://archetyped.com/tools/simple-lightbox/)
43
 
44
  == Installation ==
45
 
46
+ 1. Install and activate SLB
47
+ 1. Verify that your site's theme uses the `wp_head()`, `wp_footer()`, & `the_content()` template tags (standard in any professional theme)
 
 
48
 
49
  == Upgrade Notice ==
50
 
52
 
53
  == Frequently Asked Questions ==
54
 
55
+ Post your questions and comments on [Simple Lightbox's official page](http://archetyped.com/tools/simple-lightbox/)
56
 
57
  == Screenshots ==
58
 
60
  2. Customized UI Text
61
 
62
  == Changelog ==
63
+
64
+ = 1.6.3.1 =
65
+ * Fix: Backward compatibility restored (Backward Genius)
66
+
67
+ = 1.6.3 =
68
+ * Optimize: Improved lightbox overlay display
69
+
70
+ = 1.6.2 =
71
+ * Update: Rebuild JS code
72
+ * Optimize: Improved compatibility with PHP 5+
73
+ * Optimize: Improved utility code
74
+ * Fix: Some elements can overlap lightbox
75
+ * Fix: Slideshow not automatically starting (Laurence of Autoplay)
76
+
77
+ = 1.6.1 =
78
+ * Optimize: Handle repeated initializations of lightbox on client-side
79
+ * Fix: Localization not properly loaded (Mustafa Lingo)
80
+
81
+ = 1.6 =
82
+ * Add: Widget support
83
+ * Add: WordPress 3.3 support
84
+ * Add: Localization support
85
+ * Add: Option to group gallery links separately (supports WordPress & NextGen galleries)
86
+ * Add: Upgrade notice
87
+ * Optimize: WP 3.3 compatibility
88
+ * Optimize: Improved compatibility with URI case-sensitivity
89
+ * Optimize: Activation processing
90
+ * Optimize: Image grouping
91
+ * Optimize: Image metadata loading performance
92
+ * Optimize: File loading
93
+ * Optimize: Improved safeguards against interference by bugs in other plugins
94
+ * Optimize: Link processing performance
95
+ * Optimize: Lightbox styling isolated from site styles
96
+ * Optimize: Improved link processing performance
97
+ * Optimize: Improved image metadata support
98
+ * Optimize: Improved support for HTTP/HTTPS requests
99
+ * Fix: SLB is not defined in JS (Jezz Hands)
100
+ * Fix: Boolean case-sensitivity (78 Truths)
101
+ * Fix: YouTube embed using iFrame overlaps lightbox (Elena in Hiding)
102
+ * Fix: Issue when scanning links without valid URLs (McCloskey Iteration)
103
+ * Fix: Image activation is case-sensitive (Sensitive Tanya)
104
+ * Fix: Visible lightbox overlay edges when image larger than browser window (Chibi Overlay)
105
+ * Fix: Options availability for some users
106
+ * Fix: Inconsistent loading of image metadata
107
+ * Fix: Links not fully processed when group is set manually
108
+
109
+ = 1.5.6 =
110
+ * Add: Display image description in lightbox (with HTML support)
111
+ * Add: Support for W3 Total Cache plugin
112
+ * Add: Initial support for NextGEN galleries
113
+ * Update: **Important:** [System Requirements](http://wordpress.org/about/requirements/) aligned with WP 3.2.1
114
+ * Optimize: Improved support for small images in default template
115
+ * Optimize: Support for non-English text in user options
116
+ * Optimize: Improved IE compatibility
117
+ * Optimize: Improved data handling
118
+ * Optimize: Skin loading performance
119
+ * Optimize: Skin CSS Cleanup
120
+ * Optimize: Caption support for galleries
121
+ * Optimize: Options code cleanup (Juga Sweep)
122
+ * Fix: User-defined UI text not used (Ivan gets Even (cooler))
123
+ * Fix: Options reset after update (KRazy Donna)
124
+
125
+ = 1.5.5.1 =
126
+ * Fix: Disabled links not being disabled (Disabling Sascha)
127
+
128
+ = 1.5.5 =
129
+ * Add: Distinct link activation (will not affect other lightboxes)
130
+ * Add: Backwards compatibility with legacy lightbox links (optional)
131
+ * Add: Support for WordPress 3.2
132
+ * Add: Support for links added after page load (e.g. via AJAX, etc.)
133
+ * Add: Admin option to enable/disable attachment links
134
+ * Add: Support for image attachment links
135
+ * Update: Options management overhaul
136
+ * Update: Additional WordPress 3.2 support (Gallery)
137
+ * Update: Cache-management for enqueued files
138
+ * Update: Improved UI consistency
139
+ * Update: Improved compatibility for older versions of PHP
140
+ * Update: Internal optimizations
141
+ * Update: Improved URL handling
142
+ * Fix: Improved options migration from old versions (Hutchison Migration)
143
+ * Fix: XHTML Validation (Hajo Validation)
144
+
145
+ = 1.5.4 =
146
+ * Add: Optional Link validation
147
+ * Add: Keyboard Navigation
148
+ * Add: Option to enable/disable image caption
149
+ * Add: `rel` attribute supported again
150
+ * Add: Use `slb_off` in link's `rel` attribute to disable automatic activation for link
151
+ * Fix: HTTPS compatibility (J&uuml;rgen Protocol)
152
+ * Fix: Enabling SLB on Pages issue
153
+ * Fix: Zmanu is_single
154
+ * Fix: Image order is sometimes incorrect
155
+ * Optimize: Filter double clicks
156
+ * Optimize: Separate options to enable/disable SLB on Posts and Pages
157
+ * Optimize: Better grouping support
158
+
159
+ = 1.5.3 =
160
+ * Fix: Caption may not display under certain circumstances (Caption Erin)
161
+ * Fix: Images not grouped when "separate by post" option is activated (Logical Ross)
162
+ * Update: Lightbox will not be activated for links that already have `rel` attribute set
163
+
164
+ = 1.5.2 =
165
+ * Fix: Slideshow loops out of control (Mirage of Wallentin)
166
+ * Fix: Lightbox fails when group by posts disabled (Lange Find)
167
+ * Add: Option to use the image's URI as caption when link title not set (Under UI options)
168
+
169
+ = 1.5.1 =
170
+ * Add: WP Gallery support
171
+ * Fix: Navigation hidden when only one image
172
+ * Fix: Use user-defined UI text
173
+
174
+ = 1.5 =
175
+ * Add: Theme support
176
+ * Optimize: Javascript cleanup and file size reductions
177
+ * Optimize: CSS cleanup
178
+
179
+ = 1.4 =
180
+ * Update: Integrated with jQuery
181
+ * Optimize: Javascript filesize 9x smaller
182
+ * Add: Close lightbox by clicking to left/right outside of image (an oft-requested feature)
183
+
184
+ = 1.3.2 =
185
+ * Add: Option to enable/disable lightbox resizing animation (thanks Maria!)
186
+
187
  = 1.3.1 =
188
+ * Update: Utilities code (internal)
189
+
190
  = 1.3 =
191
+ * Add: Customizable UI label text (close, next, and prev button images can be replaced in `images` directory)
192
+ * Add: Group image links by Post (separate slideshow for each post)
193
+ * Add: Reset settings link on plugin listings page
194
+ * Optimize: Organized settings page
195
 
196
  = 1.2.1 =
197
  * Fixed: Image title given higher precedence than Image alt (more compatible w/WP workflow)
207
  * Optimized: Options menu field building
208
  * Optimized: Loading of default values for plugin options
209
  * Optimized: General code optimizations
210
+
211
  = 1.0 =
212
  * Initial release
templates/default/layout.html ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div id="slb_container">
2
+ <div id="slb_content">
3
+ {slbContent}
4
+ <div id="slb_nav_hover">
5
+ {navPrev}
6
+ {navNext}
7
+ </div>
8
+ <div id="slb_loading">
9
+ {slbLoading}
10
+ </div>
11
+ </div>
12
+ </div>
13
+ <div id="slb_details">
14
+ <div id="slb_data">
15
+ <div id="slb_data_content">
16
+ {dataCaption}
17
+ <span id="slb_data_desc">
18
+ {dataDescription}
19
+ </span>
20
+ {dataNumber}
21
+ <span id="slb_nav">
22
+ {navPrev}
23
+ {navNext}
24
+ {navSlideControl}
25
+ </span>
26
+ </div>
27
+ <div id="slb_close">
28
+ {slbClose}
29
+ </div>
30
+ </div>
31
+ </div>