pyu-activesp 0.0.4.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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