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.
@@ -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