pyu-activesp 0.0.4.1.2
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/LICENSE +25 -0
- data/README.rdoc +105 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/lib/activesp/associations.rb +76 -0
- data/lib/activesp/base.rb +139 -0
- data/lib/activesp/caching.rb +60 -0
- data/lib/activesp/connection.rb +126 -0
- data/lib/activesp/content_type.rb +152 -0
- data/lib/activesp/field.rb +193 -0
- data/lib/activesp/file.rb +71 -0
- data/lib/activesp/folder.rb +103 -0
- data/lib/activesp/ghost_field.rb +100 -0
- data/lib/activesp/group.rb +107 -0
- data/lib/activesp/item.rb +338 -0
- data/lib/activesp/list.rb +509 -0
- data/lib/activesp/permission_set.rb +64 -0
- data/lib/activesp/persistent_caching.rb +112 -0
- data/lib/activesp/role.rb +116 -0
- data/lib/activesp/root.rb +128 -0
- data/lib/activesp/site.rb +305 -0
- data/lib/activesp/url.rb +146 -0
- data/lib/activesp/user.rb +97 -0
- data/lib/activesp/util.rb +326 -0
- data/lib/activesp.rb +53 -0
- metadata +118 -0
@@ -0,0 +1,509 @@
|
|
1
|
+
# Copyright (c) 2010 XAOP bvba
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person
|
4
|
+
# obtaining a copy of this software and associated documentation
|
5
|
+
# files (the "Software"), to deal in the Software without
|
6
|
+
# restriction, including without limitation the rights to use,
|
7
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the
|
9
|
+
# Software is furnished to do so, subject to the following
|
10
|
+
# conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
#
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
#
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module ActiveSP
|
27
|
+
|
28
|
+
class List < Base
|
29
|
+
|
30
|
+
include InSite
|
31
|
+
extend Caching
|
32
|
+
extend PersistentCaching
|
33
|
+
include Util
|
34
|
+
|
35
|
+
# The containing site
|
36
|
+
# @return [Site]
|
37
|
+
attr_reader :site
|
38
|
+
# The ID of the list
|
39
|
+
# @return [String]
|
40
|
+
attr_reader :id
|
41
|
+
|
42
|
+
persistent { |site, id, *a| [site.connection, [:list, id]] }
|
43
|
+
# @private
|
44
|
+
def initialize(site, id, title = nil, attributes_before_type_cast1 = nil, attributes_before_type_cast2 = nil)
|
45
|
+
@site, @id = site, id
|
46
|
+
@Title = title if title
|
47
|
+
@attributes_before_type_cast1 = attributes_before_type_cast1 if attributes_before_type_cast1
|
48
|
+
@attributes_before_type_cast2 = attributes_before_type_cast2 if attributes_before_type_cast2
|
49
|
+
end
|
50
|
+
|
51
|
+
# The URL of the list
|
52
|
+
# @return [String]
|
53
|
+
def url
|
54
|
+
# Dirty. Used to use RootFolder, but if you get the data from the bulk calls, RootFolder is the empty
|
55
|
+
# string rather than what it should be. That's what you get with web services as an afterthought I guess.
|
56
|
+
view_url = ::File.dirname(attributes["DefaultViewUrl"])
|
57
|
+
result = URL(@site.url).join(view_url).to_s
|
58
|
+
if ::File.basename(result) == "Forms" and dir = ::File.dirname(result) and dir.length > @site.url.length
|
59
|
+
result = dir
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
cache :url
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def relative_url
|
67
|
+
@site.relative_url(url)
|
68
|
+
end
|
69
|
+
|
70
|
+
# See {Base#key}
|
71
|
+
# @return [String]
|
72
|
+
def key
|
73
|
+
encode_key("L", [@site.key, @id])
|
74
|
+
end
|
75
|
+
|
76
|
+
# @private
|
77
|
+
def Title
|
78
|
+
data1["Title"].to_s
|
79
|
+
end
|
80
|
+
cache :Title
|
81
|
+
|
82
|
+
# Yields the items in this list according to the given options. Note that this method does not
|
83
|
+
# recurse into folders. I believe specifying a folder of '' actually does recurse
|
84
|
+
# @param [Hash] options Options
|
85
|
+
# @option options [Folder, :all] :folder (nil) The folder to search in
|
86
|
+
# @option options [String] :query (nil) The query to execute as an XML fragment
|
87
|
+
# @option options [Boolean] :no_preload (nil) If set to true, the attributes are not preloaded. Can be more efficient if you only need the list of items and not their attributes
|
88
|
+
# @yieldparam [Item] item
|
89
|
+
def each_item(options = {})
|
90
|
+
options = options.dup
|
91
|
+
folder = options.delete(:folder)
|
92
|
+
# Always include a query because for some reason SP is capable of not finding certain
|
93
|
+
# items otherwise.
|
94
|
+
query = { "query" => options.delete(:query) || "" }
|
95
|
+
no_preload = options.delete(:no_preload)
|
96
|
+
options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}"
|
97
|
+
query_options = Builder::XmlMarkup.new.QueryOptions do |xml|
|
98
|
+
if folder
|
99
|
+
xml.Folder(folder == :all ? "" : folder.url)
|
100
|
+
else
|
101
|
+
xml.QeryOptions
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if no_preload
|
105
|
+
view_fields = Builder::XmlMarkup.new.ViewFields do |xml|
|
106
|
+
%w[FSObjType ID UniqueId ServerUrl].each { |f| xml.FieldRef("Name" => f) }
|
107
|
+
end
|
108
|
+
get_list_items(view_fields, query_options, query) do |attributes|
|
109
|
+
yield construct_item(folder, attributes, nil)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
__each_item(query_options, query) do |attributes|
|
113
|
+
yield construct_item(folder, attributes, attributes)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
association :items
|
118
|
+
|
119
|
+
def each_document(parameters = {}, &blk)
|
120
|
+
query = Builder::XmlMarkup.new.Query do |xml|
|
121
|
+
xml.Where do |xml|
|
122
|
+
xml.Neq do |xml|
|
123
|
+
xml.FieldRef(:Name => "FSObjType")
|
124
|
+
xml.Value(1, :Type => "Text")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
each_item(parameters.merge(:query => query), &blk)
|
129
|
+
end
|
130
|
+
association :documents do
|
131
|
+
def create(parameters = {})
|
132
|
+
@object.create_document(parameters)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def each_folder(parameters = {}, &blk)
|
137
|
+
query = Builder::XmlMarkup.new.Query do |xml|
|
138
|
+
xml.Where do |xml|
|
139
|
+
xml.Eq do |xml|
|
140
|
+
xml.FieldRef(:Name => "FSObjType")
|
141
|
+
xml.Value(1, :Type => "Text")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
each_item(parameters.merge(:query => query), &blk)
|
146
|
+
end
|
147
|
+
association :folders do
|
148
|
+
def create(parameters = {})
|
149
|
+
@object.create_folder(parameters)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the item with the given name or nil if there is no item with the given name
|
154
|
+
# @return [Item]
|
155
|
+
def item(name)
|
156
|
+
query = Builder::XmlMarkup.new.Query do |xml|
|
157
|
+
xml.Where do |xml|
|
158
|
+
xml.Eq do |xml|
|
159
|
+
xml.FieldRef(:Name => "FileLeafRef")
|
160
|
+
xml.Value(name, :Type => "Text")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
items(:query => query).first
|
165
|
+
end
|
166
|
+
|
167
|
+
alias / item
|
168
|
+
|
169
|
+
def create_document(parameters = {})
|
170
|
+
when_list { return create_list_item(parameters) }
|
171
|
+
when_document_library { return create_library_document(parameters) }
|
172
|
+
raise_on_unknown_type
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_folder(parameters = {})
|
176
|
+
name = parameters.delete("FileLeafRef") or raise ArgumentError, "Specify the folder name in the 'FileLeafRef' parameter"
|
177
|
+
|
178
|
+
create_list_item(parameters.merge(:folder_name => name))
|
179
|
+
end
|
180
|
+
|
181
|
+
def changes_since_token(token, options = {})
|
182
|
+
options = options.dup
|
183
|
+
no_preload = options.delete(:no_preload)
|
184
|
+
options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}"
|
185
|
+
|
186
|
+
if no_preload
|
187
|
+
view_fields = Builder::XmlMarkup.new.ViewFields do |xml|
|
188
|
+
%w[FSObjType ID UniqueId ServerUrl].each { |f| xml.FieldRef("Name" => f) }
|
189
|
+
end
|
190
|
+
else
|
191
|
+
view_fields = Builder::XmlMarkup.new.ViewFields
|
192
|
+
end
|
193
|
+
result = call("Lists", "get_list_item_changes_since_token", "listName" => @id, 'queryOptions' => '<QueryOptions xmlns:s="http://schemas.microsoft.com/sharepoint/soap/" ><QueryOptions/></QueryOptions>', 'changeToken' => token, 'viewFields' => view_fields)
|
194
|
+
updates = []
|
195
|
+
result.xpath("//z:row", NS).each do |row|
|
196
|
+
attributes = clean_item_attributes(row.attributes)
|
197
|
+
updates << construct_item(:unset, attributes, no_preload ? nil : attributes)
|
198
|
+
end
|
199
|
+
deletes = []
|
200
|
+
result.xpath("//sp:Changes/sp:Id", NS).each do |row|
|
201
|
+
if row["ChangeType"].to_s == "Delete"
|
202
|
+
deletes << encode_key("I", [key, row.text.to_s])
|
203
|
+
end
|
204
|
+
end
|
205
|
+
new_token = result.xpath("//sp:Changes", NS).first["LastChangeToken"].to_s
|
206
|
+
{ :updates => updates, :deletes => deletes, :new_token => new_token }
|
207
|
+
end
|
208
|
+
|
209
|
+
def fields
|
210
|
+
data1.xpath("//sp:Field", NS).map do |field|
|
211
|
+
attributes = clean_attributes(field.attributes)
|
212
|
+
if attributes["ID"] && attributes["StaticName"]
|
213
|
+
Field.new(self, attributes["ID"].downcase, attributes["StaticName"], attributes["Type"], @site.field(attributes["ID"].downcase), attributes)
|
214
|
+
end
|
215
|
+
end.compact
|
216
|
+
end
|
217
|
+
cache :fields, :dup => :always
|
218
|
+
|
219
|
+
def fields_by_name
|
220
|
+
fields.inject({}) { |h, f| h[decode_field_name(f.attributes["StaticName"])] = f ; h }
|
221
|
+
end
|
222
|
+
cache :fields_by_name, :dup => :always
|
223
|
+
|
224
|
+
# @private
|
225
|
+
def field(id)
|
226
|
+
fields.find { |f| f.ID == id }
|
227
|
+
end
|
228
|
+
|
229
|
+
def content_types
|
230
|
+
result = call("Lists", "get_list_content_types", "listName" => @id)
|
231
|
+
result.xpath("//sp:ContentType", NS).map do |content_type|
|
232
|
+
ContentType.new(@site, self, content_type["ID"], content_type["Name"], content_type["Description"], content_type["Version"], content_type["Group"])
|
233
|
+
end
|
234
|
+
end
|
235
|
+
cache :content_types, :dup => :always
|
236
|
+
|
237
|
+
# @private
|
238
|
+
def content_type(id)
|
239
|
+
content_types.find { |t| t.id == id }
|
240
|
+
end
|
241
|
+
|
242
|
+
def permission_set
|
243
|
+
if attributes["InheritedSecurity"]
|
244
|
+
@site.permission_set
|
245
|
+
else
|
246
|
+
PermissionSet.new(self)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
cache :permission_set
|
250
|
+
|
251
|
+
# See {Base#save}
|
252
|
+
# @return [void]
|
253
|
+
def save
|
254
|
+
p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes)
|
255
|
+
end
|
256
|
+
|
257
|
+
# @private
|
258
|
+
def to_s
|
259
|
+
"#<ActiveSP::List Title=#{self.Title}>"
|
260
|
+
end
|
261
|
+
|
262
|
+
# @private
|
263
|
+
alias inspect to_s
|
264
|
+
|
265
|
+
# @private
|
266
|
+
def when_document_library
|
267
|
+
yield if %w[1].include?(attributes["BaseType"])
|
268
|
+
end
|
269
|
+
|
270
|
+
# @private
|
271
|
+
def when_list
|
272
|
+
yield if %w[0 5].include?(attributes["BaseType"])
|
273
|
+
end
|
274
|
+
|
275
|
+
# @private
|
276
|
+
def raise_on_unknown_type
|
277
|
+
base_type = attributes["BaseType"]
|
278
|
+
raise "not yet BaseType = #{base_type.inspect}" unless %w[0 1 5].include?(base_type)
|
279
|
+
end
|
280
|
+
|
281
|
+
# @private
|
282
|
+
def __each_item(query_options, query)
|
283
|
+
get_list_items("<ViewFields/>", query_options, query) do |attributes|
|
284
|
+
yield attributes
|
285
|
+
end
|
286
|
+
rescue Savon::SOAPFault => e
|
287
|
+
# This is where it gets ugly... Apparently there is a limit to the number of columns
|
288
|
+
# you can retrieve with this operation. Joy!
|
289
|
+
if e.message[/lookup column threshold/]
|
290
|
+
fields = self.fields.map { |f| f.Name }
|
291
|
+
split_factor = 2
|
292
|
+
begin
|
293
|
+
split_size = (fields.length + split_factor - 1) / split_factor
|
294
|
+
parts = []
|
295
|
+
split_factor.times do |i|
|
296
|
+
lo = i * split_size
|
297
|
+
hi = [(i + 1) * split_size, fields.length].min - 1
|
298
|
+
view_fields = Builder::XmlMarkup.new.ViewFields do |xml|
|
299
|
+
fields[lo..hi].each { |f| xml.FieldRef("Name" => f) }
|
300
|
+
end
|
301
|
+
by_id = {}
|
302
|
+
get_list_items(view_fields, query_options, query) do |attributes|
|
303
|
+
by_id[attributes["ID"]] = attributes
|
304
|
+
end
|
305
|
+
parts << by_id
|
306
|
+
end
|
307
|
+
parts[0].each do |id, attrs|
|
308
|
+
parts[1..-1].each do |part|
|
309
|
+
attrs.merge!(part[id])
|
310
|
+
end
|
311
|
+
yield attrs
|
312
|
+
end
|
313
|
+
rescue Savon::SOAPFault => e
|
314
|
+
if e.message[/lookup column threshold/]
|
315
|
+
split_factor += 1
|
316
|
+
retry
|
317
|
+
else
|
318
|
+
raise
|
319
|
+
end
|
320
|
+
end
|
321
|
+
else
|
322
|
+
raise
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def ==(object)
|
327
|
+
::ActiveSP::List === object && self.ID == object.ID
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
def data1
|
333
|
+
call("Lists", "get_list", "listName" => @id).xpath("//sp:List", NS).first
|
334
|
+
end
|
335
|
+
cache :data1
|
336
|
+
|
337
|
+
def attributes_before_type_cast1
|
338
|
+
clean_attributes(data1.attributes)
|
339
|
+
end
|
340
|
+
cache :attributes_before_type_cast1
|
341
|
+
|
342
|
+
def data2
|
343
|
+
call("SiteData", "get_list", "strListName" => @id)
|
344
|
+
end
|
345
|
+
cache :data2
|
346
|
+
|
347
|
+
def attributes_before_type_cast2
|
348
|
+
element = data2.xpath("//sp:sListMetadata", NS).first
|
349
|
+
result = {}
|
350
|
+
element.children.each do |ch|
|
351
|
+
result[ch.name] = ch.inner_text
|
352
|
+
end
|
353
|
+
result
|
354
|
+
end
|
355
|
+
cache :attributes_before_type_cast2
|
356
|
+
|
357
|
+
def original_attributes
|
358
|
+
attrs = attributes_before_type_cast1.merge(attributes_before_type_cast2).merge("BaseType" => attributes_before_type_cast1["BaseType"])
|
359
|
+
type_cast_attributes(@site, nil, internal_attribute_types, attrs)
|
360
|
+
end
|
361
|
+
cache :original_attributes
|
362
|
+
|
363
|
+
def internal_attribute_types
|
364
|
+
@@internal_attribute_types ||= {
|
365
|
+
"AllowAnonymousAccess" => GhostField.new("AllowAnonymousAccess", "Bool", false, true),
|
366
|
+
"AllowDeletion" => GhostField.new("AllowDeletion", "Bool", false, true),
|
367
|
+
"AllowMultiResponses" => GhostField.new("AllowMultiResponses", "Bool", false, true),
|
368
|
+
"AnonymousPermMask" => GhostField.new("AnonymousPermMask", "Integer", false, true),
|
369
|
+
"AnonymousViewListItems" => GhostField.new("AnonymousViewListItems", "Bool", false, true),
|
370
|
+
"Author" => GhostField.new("Author", "InternalUser", false, true),
|
371
|
+
"BaseTemplate" => GhostField.new("BaseTemplate", "Text", false, true),
|
372
|
+
"BaseType" => GhostField.new("BaseType", "Text", false, true),
|
373
|
+
"Created" => GhostField.new("Created", "StandardDateTime", false, true),
|
374
|
+
"DefaultViewUrl" => GhostField.new("DefaultViewUrl", "Text", false, true),
|
375
|
+
"Description" => GhostField.new("Description", "Text", false, false),
|
376
|
+
"Direction" => GhostField.new("Direction", "Text", false, true),
|
377
|
+
"DocTemplateUrl" => GhostField.new("DocTemplateUrl", "Text", false, true),
|
378
|
+
"EmailAlias" => GhostField.new("EmailAlias", "Text", false, true),
|
379
|
+
"EmailInsertsFolder" => GhostField.new("EmailInsertsFolder", "Text", false, true),
|
380
|
+
"EnableAssignedToEmail" => GhostField.new("EnableAssignedToEmail", "Bool", false, true),
|
381
|
+
"EnableAttachments" => GhostField.new("EnableAttachments", "Bool", false, true),
|
382
|
+
"EnableMinorVersion" => GhostField.new("EnableMinorVersion", "Bool", false, true),
|
383
|
+
"EnableModeration" => GhostField.new("EnableModeration", "Bool", false, true),
|
384
|
+
"EnableVersioning" => GhostField.new("EnableVersioning", "Bool", false, true),
|
385
|
+
"EventSinkAssembly" => GhostField.new("EventSinkAssembly", "Text", false, true),
|
386
|
+
"EventSinkClass" => GhostField.new("EventSinkClass", "Text", false, true),
|
387
|
+
"EventSinkData" => GhostField.new("EventSinkData", "Text", false, true),
|
388
|
+
"FeatureId" => GhostField.new("FeatureId", "Text", false, true),
|
389
|
+
"Flags" => GhostField.new("Flags", "Integer", false, true),
|
390
|
+
"HasUniqueScopes" => GhostField.new("HasUniqueScopes", "Bool", false, true),
|
391
|
+
"Hidden" => GhostField.new("Hidden", "Bool", false, true),
|
392
|
+
"ID" => GhostField.new("ID", "Text", false, true),
|
393
|
+
"ImageUrl" => GhostField.new("ImageUrl", "Text", false, true),
|
394
|
+
"InheritedSecurity" => GhostField.new("InheritedSecurity", "Bool", false, true),
|
395
|
+
"InternalName" => GhostField.new("InternalName", "Text", false, true),
|
396
|
+
"ItemCount" => GhostField.new("ItemCount", "Integer", false, true),
|
397
|
+
"LastDeleted" => GhostField.new("LastDeleted", "StandardDateTime", false, true),
|
398
|
+
"LastModified" => GhostField.new("LastModified", "XMLDateTime", false, true),
|
399
|
+
"LastModifiedForceRecrawl" => GhostField.new("LastModifiedForceRecrawl", "XMLDateTime", false, true),
|
400
|
+
"MajorVersionLimit" => GhostField.new("MajorVersionLimit", "Integer", false, true),
|
401
|
+
"MajorWithMinorVersionsLimit" => GhostField.new("MajorWithMinorVersionsLimit", "Integer", false, true),
|
402
|
+
"MobileDefaultViewUrl" => GhostField.new("MobileDefaultViewUrl", "Text", false, true),
|
403
|
+
"Modified" => GhostField.new("Modified", "StandardDateTime", false, true),
|
404
|
+
"MultipleDataList" => GhostField.new("MultipleDataList", "Bool", false, true),
|
405
|
+
"Name" => GhostField.new("Name", "Text", false, true),
|
406
|
+
"Ordered" => GhostField.new("Ordered", "Bool", false, true),
|
407
|
+
"Permissions" => GhostField.new("Permissions", "Text", false, true),
|
408
|
+
"ReadSecurity" => GhostField.new("ReadSecurity", "Integer", false, true),
|
409
|
+
"RequireCheckout" => GhostField.new("RequireCheckout", "Bool", false, true),
|
410
|
+
"RootFolder" => GhostField.new("RootFolder", "Text", false, true),
|
411
|
+
"ScopeId" => GhostField.new("ScopeId", "Text", false, true),
|
412
|
+
"SendToLocation" => GhostField.new("SendToLocation", "Text", false, true),
|
413
|
+
"ServerTemplate" => GhostField.new("ServerTemplate", "Text", false, true),
|
414
|
+
"ShowUser" => GhostField.new("ShowUser", "Bool", false, true),
|
415
|
+
"ThumbnailSize" => GhostField.new("ThumbnailSize", "Integer", false, true),
|
416
|
+
"Title" => GhostField.new("Title", "Text", false, true),
|
417
|
+
"ValidSecurityInfo" => GhostField.new("ValidSecurityInfo", "Bool", false, true),
|
418
|
+
"Version" => GhostField.new("Version", "Integer", false, true),
|
419
|
+
"WebFullUrl" => GhostField.new("WebFullUrl", "Text", false, true),
|
420
|
+
"WebId" => GhostField.new("WebId", "Text", false, true),
|
421
|
+
"WebImageHeight" => GhostField.new("WebImageHeight", "Integer", false, true),
|
422
|
+
"WebImageWidth" => GhostField.new("WebImageWidth", "Integer", false, true),
|
423
|
+
"WorkFlowId" => GhostField.new("WorkFlowId", "Text", false, true),
|
424
|
+
"WriteSecurity" => GhostField.new("WriteSecurity", "Integer", false, true)
|
425
|
+
}
|
426
|
+
end
|
427
|
+
|
428
|
+
def permissions
|
429
|
+
result = call("Permissions", "get_permission_collection", "objectName" => @id, "objectType" => "List")
|
430
|
+
rootsite = @site.rootsite
|
431
|
+
result.xpath("//spdir:Permission", NS).map do |row|
|
432
|
+
accessor = row["MemberIsUser"][/true/i] ? User.new(rootsite, row["UserLogin"]) : Group.new(rootsite, row["GroupName"])
|
433
|
+
{ :mask => Integer(row["Mask"]), :accessor => accessor }
|
434
|
+
end
|
435
|
+
end
|
436
|
+
cache :permissions, :dup => :always
|
437
|
+
|
438
|
+
def get_list_items(view_fields, query_options, query)
|
439
|
+
result = call("Lists", "get_list_items", { "listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options }.merge(query))
|
440
|
+
result.xpath("//z:row", NS).each do |row|
|
441
|
+
yield clean_item_attributes(row.attributes)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def construct_item(folder, attributes, all_attributes)
|
446
|
+
(attributes["FSObjType"][/1$/] ? Folder : Item).new(
|
447
|
+
self,
|
448
|
+
attributes["ID"],
|
449
|
+
folder == :all ? :unset : folder,
|
450
|
+
attributes["UniqueId"],
|
451
|
+
attributes["ServerUrl"],
|
452
|
+
all_attributes
|
453
|
+
)
|
454
|
+
end
|
455
|
+
|
456
|
+
def create_library_document(parameters)
|
457
|
+
parameters = parameters.dup
|
458
|
+
content = parameters.delete(:content) or raise ArgumentError, "Specify the content in the :content parameter"
|
459
|
+
folder = parameters.delete(:folder)
|
460
|
+
overwrite = parameters.delete(:overwrite)
|
461
|
+
file_name = parameters.delete("FileLeafRef") or raise ArgumentError, "Specify the file name in the 'FileLeafRef' parameter"
|
462
|
+
raise ArgumentError, "document with file name #{file_name.inspect} already exists" if item(file_name) && !overwrite
|
463
|
+
destination_urls = Builder::XmlMarkup.new.wsdl(:string, URI.escape(::File.join(folder || url, file_name)))
|
464
|
+
parameters = type_check_attributes_for_creation(fields_by_name, parameters)
|
465
|
+
attributes = untype_cast_attributes(@site, self, fields_by_name, parameters)
|
466
|
+
fields = construct_xml_for_copy_into_items(fields_by_name, attributes)
|
467
|
+
source_url = escape_xml(file_name)
|
468
|
+
result = call("Copy", "copy_into_items", "DestinationUrls" => destination_urls, "Stream" => Base64.encode64(content.to_s), "SourceUrl" => source_url, "Fields" => fields)
|
469
|
+
copy_result = result.xpath("//sp:CopyResult", NS).first
|
470
|
+
error_code = copy_result["ErrorCode"]
|
471
|
+
if error_code != "Success"
|
472
|
+
raise "#{error_code} : #{copy_result["ErrorMessage"]}"
|
473
|
+
else
|
474
|
+
item(file_name)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def create_list_item(parameters)
|
479
|
+
parameters = parameters.dup
|
480
|
+
folder = parameters.delete(:folder)
|
481
|
+
folder_name = parameters.delete(:folder_name)
|
482
|
+
parameters = type_check_attributes_for_creation(fields_by_name, parameters)
|
483
|
+
attributes = untype_cast_attributes(@site, self, fields_by_name, parameters)
|
484
|
+
updates = Builder::XmlMarkup.new.Batch("OnError" => "Continue", "ListVersion" => 1) do |xml|
|
485
|
+
xml.Method("ID" => 1, "Cmd" => "New") do
|
486
|
+
xml.Field("New", "Name" => "ID")
|
487
|
+
construct_xml_for_update_list_items(xml, fields_by_name, attributes)
|
488
|
+
if folder_name
|
489
|
+
xml.Field(::File.join(folder || url, folder_name), "Name" => "FileRef")
|
490
|
+
xml.Field(1, "Name" => "FSObjType")
|
491
|
+
else
|
492
|
+
xml.Field(::File.join(folder, Time.now.strftime("%Y%m%d%H%M%S-#{rand(16**3).to_s(16)}")), "Name" => "FileRef") if folder
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
result = call("Lists", "update_list_items", "listName" => self.id, "updates" => updates)
|
497
|
+
create_result = result.xpath("//sp:Result", NS).first
|
498
|
+
error_text = create_result.xpath("./sp:ErrorText", NS).first
|
499
|
+
if !error_text
|
500
|
+
row = result.xpath("//z:row", NS).first
|
501
|
+
construct_item(nil, clean_item_attributes(row.attributes), nil)
|
502
|
+
else
|
503
|
+
raise "cannot create item: #{error_text.text.to_s}"
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
end
|
508
|
+
|
509
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright (c) 2010 XAOP bvba
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person
|
4
|
+
# obtaining a copy of this software and associated documentation
|
5
|
+
# files (the "Software"), to deal in the Software without
|
6
|
+
# restriction, including without limitation the rights to use,
|
7
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the
|
9
|
+
# Software is furnished to do so, subject to the following
|
10
|
+
# conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
#
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
#
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module ActiveSP
|
27
|
+
|
28
|
+
class PermissionSet
|
29
|
+
|
30
|
+
include Util
|
31
|
+
|
32
|
+
attr_reader :scope
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def initialize(scope)
|
36
|
+
@scope = scope
|
37
|
+
end
|
38
|
+
|
39
|
+
# See {Base#key}
|
40
|
+
# @return [String]
|
41
|
+
def key
|
42
|
+
encode_key("P", [@scope.key])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the permissions in this permission set as an array of hashes with :accessor mapping to a user,
|
46
|
+
# group or role and :mask mapping to the permission as an integer
|
47
|
+
# @return [Array<Hash{:accessor, :permission => User, Group, Role, Integer}>]
|
48
|
+
# @example
|
49
|
+
# set.permissions #=> [{:accessor=>#<ActiveSP::User login_name=SHAREPOINT\system>, :mask=>134287360}]
|
50
|
+
def permissions
|
51
|
+
@scope.send(:permissions)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @private
|
55
|
+
def to_s
|
56
|
+
"#<ActiveSP::PermissionSet scope=#{@scope}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
# @private
|
60
|
+
alias inspect to_s
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Copyright (c) 2010 XAOP bvba
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person
|
4
|
+
# obtaining a copy of this software and associated documentation
|
5
|
+
# files (the "Software"), to deal in the Software without
|
6
|
+
# restriction, including without limitation the rights to use,
|
7
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the
|
9
|
+
# Software is furnished to do so, subject to the following
|
10
|
+
# conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
#
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
#
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module ActiveSP
|
27
|
+
|
28
|
+
# @private
|
29
|
+
module PersistentCaching
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def persistent(&blk)
|
34
|
+
class << self ; self ; end.instance_eval do
|
35
|
+
alias_method :old_new, :new
|
36
|
+
define_method(:new) do |*a|
|
37
|
+
cache_scope, indices = *blk.call(a)
|
38
|
+
if cache_scope.respond_to?(:persistent_cache)
|
39
|
+
cache_scope.persistent_cache.lookup(indices) { old_new(*a) }
|
40
|
+
else
|
41
|
+
old_new(*a)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
class PersistentCache
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@cache = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def lookup(indices)
|
57
|
+
if o = @cache[indices]
|
58
|
+
# puts " Cache hit for #{indices.inspect}"
|
59
|
+
else
|
60
|
+
o = @cache[indices] ||= yield
|
61
|
+
# puts " Cache miss for #{indices.inspect}"
|
62
|
+
end
|
63
|
+
o
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
module PersistentCachingConfig
|
69
|
+
|
70
|
+
# Configures the scope of the persistent cache. The default scope of the cache
|
71
|
+
# is the {Connection} object, i.e., each connection has its own cache. For example
|
72
|
+
# you can use this to make thread local caches. Note that the cache is not actually
|
73
|
+
# thread safe at this point so this may not be such a bad idea.
|
74
|
+
#
|
75
|
+
# Caching in ActiveSP at the moment is very aggressive. What this means that everything
|
76
|
+
# you ever accessed will be cached. You can override the cache for a particular object
|
77
|
+
# by calling {Base#reload} on it. One advantage of this caching strategy is that every time
|
78
|
+
# you access an object in SharePoint, it is guaranteed to be the same object in Ruby as
|
79
|
+
# well, irrespective of how you obtained a reference to that object. This eliminates a
|
80
|
+
# whole slew of issues, but you need to be aware of this.
|
81
|
+
#
|
82
|
+
# This method expects a block to which a new cache object is passed. The idea is that
|
83
|
+
# you store this cache object in a place that reflects the scope of your cache. If you
|
84
|
+
# already had a cache object stored for you current scope, you do not do anything with
|
85
|
+
# the cache object. The cache object that the block returns is the cache that will be used.
|
86
|
+
# Note that the block is called everytime ActiveSP needs the cache, so make it as
|
87
|
+
# efficient as possible. The example below illustrates how you can use the ||= operator
|
88
|
+
# for this to get a thread local cache.
|
89
|
+
#
|
90
|
+
# You can use this block to return a cache of your own. A cache is only expected to have
|
91
|
+
# a lookup method to which the cache key is passed (do not assume it is an integer or a
|
92
|
+
# string because it is not) as parameter and is expected to return the value for that
|
93
|
+
# key. In case of a cache miss, the method should yield to retrieve the value, store it
|
94
|
+
# with the given key and return the value. You can use this to plug in a cache that has
|
95
|
+
# a limited size, or that uses weak references to clean up the cache. The latter suggestion
|
96
|
+
# is a lot safer than the former!
|
97
|
+
#
|
98
|
+
# @example How to configure caching strategy
|
99
|
+
# c = ActiveSP::Connection.new(:login => l, :password => p, :root => r)
|
100
|
+
# c.configure_persistent_cache { |cache| Thread.current[:sp_cache] ||= cache }
|
101
|
+
def configure_persistent_cache(&blk)
|
102
|
+
@last_persistent_cache_object = PersistentCache.new
|
103
|
+
class << self ; self ; end.send(:define_method, :persistent_cache) do
|
104
|
+
cache = blk.call(@last_persistent_cache_object)
|
105
|
+
@last_persistent_cache_object = PersistentCache.new if cache == @last_persistent_cache_object
|
106
|
+
cache
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|