treequel 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +163 -76
- data/History.rdoc +11 -0
- data/README.rdoc +14 -16
- data/Rakefile +6 -1
- data/bin/treequel +4 -10
- data/bin/treewhat +2 -4
- data/lib/treequel/branch.rb +49 -163
- data/lib/treequel/branchcollection.rb +11 -16
- data/lib/treequel/branchset.rb +24 -17
- data/lib/treequel/control.rb +8 -6
- data/lib/treequel/controls/contentsync.rb +2 -2
- data/lib/treequel/controls/pagedresults.rb +1 -10
- data/lib/treequel/directory.rb +36 -78
- data/lib/treequel/exceptions.rb +1 -2
- data/lib/treequel/filter.rb +3 -8
- data/lib/treequel/mixins.rb +2 -7
- data/lib/treequel/model/errors.rb +1 -4
- data/lib/treequel/model/objectclass.rb +14 -26
- data/lib/treequel/model.rb +18 -64
- data/lib/treequel/monkeypatches.rb +2 -3
- data/lib/treequel/schema/attributetype.rb +0 -5
- data/lib/treequel/schema/objectclass.rb +9 -27
- data/lib/treequel/schema.rb +1 -18
- data/lib/treequel/utils.rb +0 -3
- data/lib/treequel.rb +48 -75
- data/spec/treequel/branchset_spec.rb +24 -0
- data/spec/treequel/monkeypatches_spec.rb +9 -9
- data.tar.gz.sig +0 -0
- metadata +27 -27
- metadata.gz.sig +1 -3
data/lib/treequel.rb
CHANGED
@@ -13,8 +13,7 @@ require 'uri/ldap'
|
|
13
13
|
|
14
14
|
### Add an LDAPS URI type if none exists (ruby pre 1.8.7)
|
15
15
|
unless URI.const_defined?( :LDAPS )
|
16
|
-
#
|
17
|
-
module URI
|
16
|
+
module URI # :nodoc:
|
18
17
|
class LDAPS < LDAP
|
19
18
|
DEFAULT_PORT = 636
|
20
19
|
end
|
@@ -23,40 +22,14 @@ unless URI.const_defined?( :LDAPS )
|
|
23
22
|
end
|
24
23
|
|
25
24
|
|
26
|
-
# A library for interacting with LDAP modelled after Sequel.
|
27
|
-
#
|
28
|
-
# @version 1.5.3
|
29
|
-
#
|
30
|
-
# @example
|
31
|
-
# # Connect to the directory at the specified URL
|
32
|
-
# dir = Treequel.directory( 'ldap://ldap.company.com/dc=company,dc=com' )
|
33
|
-
#
|
34
|
-
# # Get a list of email addresses of every person in the directory (as
|
35
|
-
# # long as people are under ou=people)
|
36
|
-
# dir.ou( :people ).filter( :mail ).map( :mail ).flatten
|
37
|
-
#
|
38
|
-
# # Get a list of all IP addresses for all hosts in any ou=hosts group
|
39
|
-
# # in the whole directory:
|
40
|
-
# dir.filter( :ou => :hosts ).collection.filter( :ipHostNumber ).
|
41
|
-
# map( :ipHostNumber ).flatten
|
42
|
-
#
|
43
|
-
# # Get all people in the directory in the form of a hash of names
|
44
|
-
# # keyed by email addresses
|
45
|
-
# dir.ou( :people ).filter( :mail ).to_hash( :mail, :cn )
|
46
|
-
#
|
47
|
-
# @author Michael Granger <ged@FaerieMUD.org>
|
48
|
-
# @author Mahlon E. Smith <mahlon@martini.nu>
|
49
|
-
#
|
50
|
-
# @see LICENSE Please see the LICENSE file in the base directory for licensing
|
51
|
-
# details.
|
52
|
-
#
|
25
|
+
# A library for interacting with LDAP modelled after Sequel[http://sequel.rubyforge.org/].
|
53
26
|
module Treequel
|
54
27
|
|
55
28
|
# Library version
|
56
|
-
VERSION = '1.
|
29
|
+
VERSION = '1.7.0'
|
57
30
|
|
58
31
|
# VCS revision
|
59
|
-
REVISION = %q$Revision:
|
32
|
+
REVISION = %q$Revision: c5b83ddad969 $
|
60
33
|
|
61
34
|
# Common paths for ldap.conf
|
62
35
|
COMMON_LDAP_CONF_PATHS = %w[
|
@@ -79,7 +52,18 @@ module Treequel
|
|
79
52
|
include Treequel::Constants
|
80
53
|
|
81
54
|
|
82
|
-
###
|
55
|
+
### Get the Treequel version.
|
56
|
+
def self::version_string( include_buildnum=false )
|
57
|
+
vstring = "%s %s" % [ self.name, VERSION ]
|
58
|
+
vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
|
59
|
+
return vstring
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
#
|
64
|
+
# :section: Logging
|
65
|
+
#
|
66
|
+
|
83
67
|
# Log levels
|
84
68
|
LOG_LEVELS = {
|
85
69
|
'debug' => Logger::DEBUG,
|
@@ -100,14 +84,13 @@ module Treequel
|
|
100
84
|
|
101
85
|
|
102
86
|
class << self
|
103
|
-
#
|
104
|
-
# subsystem is reset
|
87
|
+
# The log formatter that will be used when the logging subsystem is reset
|
105
88
|
attr_accessor :default_log_formatter
|
106
89
|
|
107
|
-
#
|
90
|
+
# The logger that will be used when the logging subsystem is reset
|
108
91
|
attr_accessor :default_logger
|
109
92
|
|
110
|
-
#
|
93
|
+
# The logger that's currently in effect
|
111
94
|
attr_accessor :logger
|
112
95
|
alias_method :log, :logger
|
113
96
|
alias_method :log=, :logger=
|
@@ -115,7 +98,6 @@ module Treequel
|
|
115
98
|
|
116
99
|
|
117
100
|
### Reset the global logger object to the default
|
118
|
-
### @return [void]
|
119
101
|
def self::reset_logger
|
120
102
|
self.logger = self.default_logger
|
121
103
|
self.logger.level = Logger::WARN
|
@@ -130,30 +112,30 @@ module Treequel
|
|
130
112
|
end
|
131
113
|
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
###
|
143
|
-
###
|
144
|
-
###
|
145
|
-
###
|
146
|
-
###
|
147
|
-
###
|
148
|
-
###
|
149
|
-
###
|
150
|
-
###
|
151
|
-
###
|
152
|
-
###
|
153
|
-
###
|
154
|
-
###
|
155
|
-
###
|
156
|
-
###
|
115
|
+
#
|
116
|
+
# :section:
|
117
|
+
#
|
118
|
+
|
119
|
+
### :call-seq:
|
120
|
+
### Treequel.directory( uri )
|
121
|
+
### Treequel.directory( options )
|
122
|
+
###
|
123
|
+
### Create a Treequel::Directory object, either from an LDAP URL or a Hash of connection
|
124
|
+
### options. The valid options are:
|
125
|
+
###
|
126
|
+
### [:host]
|
127
|
+
### The LDAP host to connect to; default is 'localhost'.
|
128
|
+
### [:port]
|
129
|
+
### The port number to connect to; defaults to LDAP::LDAP_PORT.
|
130
|
+
### [:connect_type]
|
131
|
+
### The type of connection to establish; :tls, :ssl, or :plain. Defaults to :tls.
|
132
|
+
### [:base_dn]
|
133
|
+
### The base DN of the directory.
|
134
|
+
### [:bind_dn]
|
135
|
+
### The DN of the user to bind as.
|
136
|
+
### [:pass]
|
137
|
+
### The password to use when binding.
|
138
|
+
###
|
157
139
|
def self::directory( *args )
|
158
140
|
options = {}
|
159
141
|
|
@@ -173,11 +155,9 @@ module Treequel
|
|
173
155
|
|
174
156
|
|
175
157
|
### Read the configuration from the specified +configfile+ and/or values in ENV and return
|
176
|
-
### a
|
158
|
+
### a Treequel::Directory for the resulting configuration. Supports OpenLDAP and nss-style
|
177
159
|
### configuration-file directives, and honors the various OpenLDAP environment variables;
|
178
160
|
### see ldap.conf(5) for details.
|
179
|
-
### @param [String] configfile the path to the configuration file to use
|
180
|
-
### @return [Treequel::Directory] the configured Directory object
|
181
161
|
def self::directory_from_config( configfile=nil )
|
182
162
|
configfile ||= self.find_configfile or
|
183
163
|
raise ArgumentError, "No configfile specified, and no defaults present."
|
@@ -195,8 +175,6 @@ module Treequel
|
|
195
175
|
|
196
176
|
### Make an options hash suitable for passing to Treequel::Directory.new from the
|
197
177
|
### given +uri+.
|
198
|
-
### @param [URI, String] uri the URI to parse for options
|
199
|
-
### @return [Hash] the parsed options
|
200
178
|
def self::make_options_from_uri( uri )
|
201
179
|
uri = URI( uri ) unless uri.is_a?( URI )
|
202
180
|
raise ArgumentError, "not an LDAP URL: %p" % [ uri ] unless
|
@@ -224,8 +202,6 @@ module Treequel
|
|
224
202
|
|
225
203
|
### Find a valid ldap.conf config file by first looking in the LDAPCONF and LDAPRC environment
|
226
204
|
### variables, then searching the list of default paths in Treequel::COMMON_LDAP_CONF_PATHS.
|
227
|
-
### @return [String] the first valid path, or nil if no valid configfile could be found
|
228
|
-
### @raise [RuntimeError] if LDAPCONF or LDAPRC contains an invalid or unreadable path
|
229
205
|
def self::find_configfile
|
230
206
|
# LDAPCONF may be set to the path of a configuration file. This path can
|
231
207
|
# be absolute or relative to the current working directory.
|
@@ -255,10 +231,8 @@ module Treequel
|
|
255
231
|
end
|
256
232
|
|
257
233
|
|
258
|
-
### Read the ldap.conf-style configuration from +configfile+ and return it as a Hash
|
259
|
-
###
|
260
|
-
### @return [Hash] The hash of configuration values read from the file, in a form suitable for
|
261
|
-
### passing to {Treequel::Directory#initialize}.
|
234
|
+
### Read the ldap.conf-style configuration from +configfile+ and return it as a Hash suitable
|
235
|
+
### for passing to Treequel::Directory.new.
|
262
236
|
def self::read_opts_from_config( configfile )
|
263
237
|
Treequel.log.info "Reading config options from %s..." % [ configfile ]
|
264
238
|
opts = {}
|
@@ -329,9 +303,8 @@ module Treequel
|
|
329
303
|
end
|
330
304
|
|
331
305
|
|
332
|
-
### Read OpenLDAP-style connection options from ENV and return them as a Hash
|
333
|
-
###
|
334
|
-
### suitable for passing to {Treequel::Directory#initialize}.
|
306
|
+
### Read OpenLDAP-style connection options from ENV and return them as a Hash suitable for
|
307
|
+
### passing to Treequel::Directory.new.
|
335
308
|
def self::read_opts_from_environment
|
336
309
|
opts = {}
|
337
310
|
|
@@ -213,6 +213,16 @@ describe Treequel::Branchset do
|
|
213
213
|
@branchset.first.should == :first_matching_branch
|
214
214
|
end
|
215
215
|
|
216
|
+
it "performs a search using the default filter, scope, and a limit of 5 when the first " +
|
217
|
+
"five records are requested" do
|
218
|
+
params = @params.merge( :limit => 5 )
|
219
|
+
@branch.should_receive( :search ).
|
220
|
+
with( Treequel::Branchset::DEFAULT_SCOPE, @branchset.filter, params ).
|
221
|
+
and_return( [:branch1, :branch2, :branch3, :branch4, :branch5] )
|
222
|
+
|
223
|
+
@branchset.first( 5 ).should == [:branch1, :branch2, :branch3, :branch4, :branch5]
|
224
|
+
end
|
225
|
+
|
216
226
|
it "creates a new branchset cloned from itself with the specified filter" do
|
217
227
|
newset = @branchset.filter( :clothing, 'pants' )
|
218
228
|
newset.filter_string.should == '(clothing=pants)'
|
@@ -236,6 +246,19 @@ describe Treequel::Branchset do
|
|
236
246
|
}.to raise_exception( Treequel::ExpressionError, /no existing filter/i )
|
237
247
|
end
|
238
248
|
|
249
|
+
|
250
|
+
#
|
251
|
+
# not
|
252
|
+
#
|
253
|
+
it "can create a new branchset cloned from itself with a NOT clause added to an " +
|
254
|
+
"existing filter" do
|
255
|
+
pantset = @branchset.filter( :clothing => 'pants' )
|
256
|
+
notsmallset = pantset.not( :size => 'small' )
|
257
|
+
|
258
|
+
notsmallset.filter_string.should == '(&(clothing=pants)(!(size=small)))'
|
259
|
+
end
|
260
|
+
|
261
|
+
|
239
262
|
#
|
240
263
|
# #scope
|
241
264
|
#
|
@@ -427,6 +450,7 @@ describe Treequel::Branchset do
|
|
427
450
|
newset.options[:select].should include( :+ )
|
428
451
|
end
|
429
452
|
|
453
|
+
|
430
454
|
end
|
431
455
|
|
432
456
|
|
@@ -75,11 +75,11 @@ describe Treequel::TimeExtensions do
|
|
75
75
|
before( :all ) do
|
76
76
|
# Make the local timezone PDT so offsets show up correctly
|
77
77
|
@real_tz = ENV['TZ']
|
78
|
-
ENV['TZ'] = '
|
78
|
+
ENV['TZ'] = ':GMT'
|
79
79
|
end
|
80
80
|
|
81
81
|
before( :each ) do
|
82
|
-
@time = Time.parse( "Fri Aug 20 08:21:35.1876455 -
|
82
|
+
@time = Time.parse( "Fri Aug 20 08:21:35.1876455 -0000 2010" )
|
83
83
|
end
|
84
84
|
|
85
85
|
after( :all ) do
|
@@ -89,23 +89,23 @@ describe Treequel::TimeExtensions do
|
|
89
89
|
describe "RFC4517 LDAP Generalized Time method" do
|
90
90
|
|
91
91
|
it "returns the time in 'Generalized Time' format" do
|
92
|
-
@time.ldap_generalized.should == "
|
92
|
+
@time.ldap_generalized.should == "20100820082135Z"
|
93
93
|
end
|
94
94
|
|
95
95
|
it "can include fractional seconds if the optional fractional digits argument is given" do
|
96
|
-
@time.ldap_generalized( 3 ).should == "20100820082135.
|
96
|
+
@time.ldap_generalized( 3 ).should == "20100820082135.187Z"
|
97
97
|
end
|
98
98
|
|
99
99
|
it "doesn't include the decimal if fractional digits is specified but zero" do
|
100
|
-
@time.ldap_generalized( 0 ).should == "
|
100
|
+
@time.ldap_generalized( 0 ).should == "20100820082135Z"
|
101
101
|
end
|
102
102
|
|
103
103
|
it "zero-fills any digits after six in the fractional digits" do
|
104
|
-
@time.ldap_generalized( 11 ).should == "20100820082135.
|
104
|
+
@time.ldap_generalized( 11 ).should == "20100820082135.18764500000Z"
|
105
105
|
end
|
106
106
|
|
107
107
|
it "uses 'Z' for the timezone of times in UTC" do
|
108
|
-
@time.utc.ldap_generalized.should == "
|
108
|
+
@time.utc.ldap_generalized.should == "20100820082135Z"
|
109
109
|
end
|
110
110
|
|
111
111
|
end
|
@@ -113,11 +113,11 @@ describe Treequel::TimeExtensions do
|
|
113
113
|
describe "RFC4517 UTC Time method" do
|
114
114
|
|
115
115
|
it "returns the time in 'UTC Time' format" do
|
116
|
-
@time.ldap_utc.should == "
|
116
|
+
@time.ldap_utc.should == "100820082135Z"
|
117
117
|
end
|
118
118
|
|
119
119
|
it "uses 'Z' for the timezone of times in UTC" do
|
120
|
-
@time.utc.ldap_utc.should == "
|
120
|
+
@time.utc.ldap_utc.should == "100820082135Z"
|
121
121
|
end
|
122
122
|
|
123
123
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: treequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -49,11 +49,11 @@ cert_chain:
|
|
49
49
|
-----END CERTIFICATE-----
|
50
50
|
|
51
51
|
'
|
52
|
-
date: 2011-10
|
52
|
+
date: 2011-11-10 00:00:00.000000000 Z
|
53
53
|
dependencies:
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: ruby-ldap
|
56
|
-
requirement: &
|
56
|
+
requirement: &70222008767240 !ruby/object:Gem::Requirement
|
57
57
|
none: false
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
@@ -61,10 +61,10 @@ dependencies:
|
|
61
61
|
version: '0.9'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
|
-
version_requirements: *
|
64
|
+
version_requirements: *70222008767240
|
65
65
|
- !ruby/object:Gem::Dependency
|
66
66
|
name: diff-lcs
|
67
|
-
requirement: &
|
67
|
+
requirement: &70222008766820 !ruby/object:Gem::Requirement
|
68
68
|
none: false
|
69
69
|
requirements:
|
70
70
|
- - ~>
|
@@ -72,10 +72,10 @@ dependencies:
|
|
72
72
|
version: '1.1'
|
73
73
|
type: :runtime
|
74
74
|
prerelease: false
|
75
|
-
version_requirements: *
|
75
|
+
version_requirements: *70222008766820
|
76
76
|
- !ruby/object:Gem::Dependency
|
77
77
|
name: hoe-mercurial
|
78
|
-
requirement: &
|
78
|
+
requirement: &70222008766400 !ruby/object:Gem::Requirement
|
79
79
|
none: false
|
80
80
|
requirements:
|
81
81
|
- - ~>
|
@@ -83,10 +83,10 @@ dependencies:
|
|
83
83
|
version: 1.3.1
|
84
84
|
type: :development
|
85
85
|
prerelease: false
|
86
|
-
version_requirements: *
|
86
|
+
version_requirements: *70222008766400
|
87
87
|
- !ruby/object:Gem::Dependency
|
88
88
|
name: hoe-manualgen
|
89
|
-
requirement: &
|
89
|
+
requirement: &70222008782300 !ruby/object:Gem::Requirement
|
90
90
|
none: false
|
91
91
|
requirements:
|
92
92
|
- - ~>
|
@@ -94,10 +94,10 @@ dependencies:
|
|
94
94
|
version: 0.2.0
|
95
95
|
type: :development
|
96
96
|
prerelease: false
|
97
|
-
version_requirements: *
|
97
|
+
version_requirements: *70222008782300
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: hoe-highline
|
100
|
-
requirement: &
|
100
|
+
requirement: &70222008781880 !ruby/object:Gem::Requirement
|
101
101
|
none: false
|
102
102
|
requirements:
|
103
103
|
- - ~>
|
@@ -105,21 +105,21 @@ dependencies:
|
|
105
105
|
version: 0.0.1
|
106
106
|
type: :development
|
107
107
|
prerelease: false
|
108
|
-
version_requirements: *
|
108
|
+
version_requirements: *70222008781880
|
109
109
|
- !ruby/object:Gem::Dependency
|
110
110
|
name: rspec
|
111
|
-
requirement: &
|
111
|
+
requirement: &70222008781420 !ruby/object:Gem::Requirement
|
112
112
|
none: false
|
113
113
|
requirements:
|
114
114
|
- - ~>
|
115
115
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2.
|
116
|
+
version: '2.7'
|
117
117
|
type: :development
|
118
118
|
prerelease: false
|
119
|
-
version_requirements: *
|
119
|
+
version_requirements: *70222008781420
|
120
120
|
- !ruby/object:Gem::Dependency
|
121
121
|
name: ruby-termios
|
122
|
-
requirement: &
|
122
|
+
requirement: &70222008781000 !ruby/object:Gem::Requirement
|
123
123
|
none: false
|
124
124
|
requirements:
|
125
125
|
- - ~>
|
@@ -127,10 +127,10 @@ dependencies:
|
|
127
127
|
version: '0.9'
|
128
128
|
type: :development
|
129
129
|
prerelease: false
|
130
|
-
version_requirements: *
|
130
|
+
version_requirements: *70222008781000
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
132
|
name: ruby-terminfo
|
133
|
-
requirement: &
|
133
|
+
requirement: &70222008780580 !ruby/object:Gem::Requirement
|
134
134
|
none: false
|
135
135
|
requirements:
|
136
136
|
- - ~>
|
@@ -138,10 +138,10 @@ dependencies:
|
|
138
138
|
version: '0.1'
|
139
139
|
type: :development
|
140
140
|
prerelease: false
|
141
|
-
version_requirements: *
|
141
|
+
version_requirements: *70222008780580
|
142
142
|
- !ruby/object:Gem::Dependency
|
143
143
|
name: columnize
|
144
|
-
requirement: &
|
144
|
+
requirement: &70222008780160 !ruby/object:Gem::Requirement
|
145
145
|
none: false
|
146
146
|
requirements:
|
147
147
|
- - ~>
|
@@ -149,10 +149,10 @@ dependencies:
|
|
149
149
|
version: '0.3'
|
150
150
|
type: :development
|
151
151
|
prerelease: false
|
152
|
-
version_requirements: *
|
152
|
+
version_requirements: *70222008780160
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: sysexits
|
155
|
-
requirement: &
|
155
|
+
requirement: &70222008779740 !ruby/object:Gem::Requirement
|
156
156
|
none: false
|
157
157
|
requirements:
|
158
158
|
- - ~>
|
@@ -160,10 +160,10 @@ dependencies:
|
|
160
160
|
version: '1.0'
|
161
161
|
type: :development
|
162
162
|
prerelease: false
|
163
|
-
version_requirements: *
|
163
|
+
version_requirements: *70222008779740
|
164
164
|
- !ruby/object:Gem::Dependency
|
165
165
|
name: sequel
|
166
|
-
requirement: &
|
166
|
+
requirement: &70222008779320 !ruby/object:Gem::Requirement
|
167
167
|
none: false
|
168
168
|
requirements:
|
169
169
|
- - ~>
|
@@ -171,10 +171,10 @@ dependencies:
|
|
171
171
|
version: '3.20'
|
172
172
|
type: :development
|
173
173
|
prerelease: false
|
174
|
-
version_requirements: *
|
174
|
+
version_requirements: *70222008779320
|
175
175
|
- !ruby/object:Gem::Dependency
|
176
176
|
name: hoe
|
177
|
-
requirement: &
|
177
|
+
requirement: &70222008778900 !ruby/object:Gem::Requirement
|
178
178
|
none: false
|
179
179
|
requirements:
|
180
180
|
- - ~>
|
@@ -182,7 +182,7 @@ dependencies:
|
|
182
182
|
version: '2.12'
|
183
183
|
type: :development
|
184
184
|
prerelease: false
|
185
|
-
version_requirements: *
|
185
|
+
version_requirements: *70222008778900
|
186
186
|
description: ! "Treequel is an LDAP toolkit for Ruby. It is intended to allow quick,
|
187
187
|
easy\naccess to LDAP directories in a manner consistent with LDAP's hierarchical,\nfree-form
|
188
188
|
nature. \n\nIt's inspired by and modeled after [Sequel](http://sequel.rubyforge.org/),
|
metadata.gz.sig
CHANGED