From d982058f46faa02514a6ab5d279e1d3e64d07eaf Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Wed, 8 Jun 2011 19:06:20 +0200 Subject: [PATCH 01/32] Update to Savon 0.9.2 and add preliminary support for GSSNEGOTIATE --- activesp.gemspec | 5 +-- lib/activesp/connection.rb | 54 +++++++++++++++++++++++++++++-- lib/activesp/content_type.rb | 4 +-- lib/activesp/errors.rb | 3 ++ lib/activesp/file.rb | 2 +- lib/activesp/group.rb | 4 +-- lib/activesp/item.rb | 29 ++++++++++------- lib/activesp/list.rb | 26 +++++++-------- lib/activesp/role.rb | 6 ++-- lib/activesp/site.rb | 63 +++++++++++++++++++++--------------- lib/activesp/user.rb | 2 +- 11 files changed, 134 insertions(+), 64 deletions(-) diff --git a/activesp.gemspec b/activesp.gemspec index 8f2fb28..bad54f2 100644 --- a/activesp.gemspec +++ b/activesp.gemspec @@ -12,8 +12,9 @@ Gem::Specification.new do |s| s.files += Dir['lib/**/*.rb'] # s.bindir = "bin" # s.executables.push(*(Dir['bin/*.rb'])) - s.add_dependency('savon-xaop', '= 0.7.2.7') - s.add_dependency('nokogiri') + s.add_dependency('savon', '= 0.9.2') + s.add_dependency('curb') + s.add_dependency('httpi', '= 0.9.4') # s.rdoc_options << '--exclude' << 'ext' << '--main' << 'README' # s.extra_rdoc_files = ["README"] s.has_rdoc = false diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 3504407..9566edf 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -26,10 +26,58 @@ require 'savon' require 'net/ntlm_http' -Savon::Request.logger.level = Logger::ERROR +Savon.configure do |config| + config.log = false +end + +HTTPI.log = false + +HTTPI.adapter = :curb + +class Savon::SOAP::Fault + + def error_code + Integer(((to_hash[:fault] || {})[:detail] || {})[:errorcode] || 0) + end + + def error_string + ((to_hash[:fault] || {})[:detail] || {})[:errorstring] + end + +end -Savon::Response.error_handler do |soap_fault| - soap_fault[:detail][:errorstring] +class HTTPI::Auth::Config + + # Accessor for the GSSNEGOTIATE auth credentials. + def gssnegotiate(*args) + return @gssnegotiate if args.empty? + + self.type = :gssnegotiate + @gssnegotiate = args.flatten.compact + end + + # Returns whether to use GSSNEGOTIATE auth. + def gssnegotiate? + type == :gssnegotiate + end + +end + +class HTTPI::Adapter::Curb + + def setup_client(request) + basic_setup request + setup_http_auth request if request.auth.http? + setup_ssl_auth request.auth.ssl if request.auth.ssl? + setup_ntlm_auth request if request.auth.ntlm? + setup_gssnegotiate_auth request if request.auth.gssnegotiate? + end + + def setup_gssnegotiate_auth(request) + client.username, client.password = *request.auth.credentials + client.http_auth_types = request.auth.type + end + end module ActiveSP diff --git a/lib/activesp/content_type.rb b/lib/activesp/content_type.rb index dbe68d5..a4390a3 100644 --- a/lib/activesp/content_type.rb +++ b/lib/activesp/content_type.rb @@ -121,9 +121,9 @@ def to_s def data if @list - call("Lists", "get_list_content_type", "listName" => @list.id, "contentTypeId" => @id).xpath("//sp:ContentType", NS).first + call("Lists", "GetListContentType", "listName" => @list.id, "contentTypeId" => @id).xpath("//sp:ContentType", NS).first else - call("Webs", "get_content_type", "contentTypeId" => @id).xpath("//sp:ContentType", NS).first + call("Webs", "GetContentType", "contentTypeId" => @id).xpath("//sp:ContentType", NS).first end end cache :data diff --git a/lib/activesp/errors.rb b/lib/activesp/errors.rb index 1ec803f..e886ea7 100644 --- a/lib/activesp/errors.rb +++ b/lib/activesp/errors.rb @@ -3,6 +3,9 @@ module ActiveSP class AccessDenied < Exception end + class PermissionDenied < Exception + end + class AlreadyExists < Exception def initialize(msg, &object_blk) diff --git a/lib/activesp/file.rb b/lib/activesp/file.rb index d1717c5..86196b4 100644 --- a/lib/activesp/file.rb +++ b/lib/activesp/file.rb @@ -54,7 +54,7 @@ def content_size def destroy if @destroyable - result = call("Lists", "delete_attachment", "listName" => @item.list.id, "listItemID" => @item.ID, "url" => @url) + result = call("Lists", "DeleteAttachment", "listName" => @item.list.id, "listItemID" => @item.ID, "url" => @url) if delete_result = result.xpath("//sp:DeleteAttachmentResponse", NS).first @item.clear_cache_for(:attachment_urls) self diff --git a/lib/activesp/group.rb b/lib/activesp/group.rb index f4199a4..0448d38 100644 --- a/lib/activesp/group.rb +++ b/lib/activesp/group.rb @@ -47,7 +47,7 @@ def key # Returns the list of users in this group # @return [User] def users - call("UserGroup", "get_user_collection_from_group", "groupName" => @name).xpath("//spdir:User", NS).map do |row| + call("UserGroup", "GetUserCollectionFromGroup", "groupName" => @name).xpath("//spdir:User", NS).map do |row| attributes = clean_attributes(row.attributes) User.new(@site, attributes["LoginName"]) end @@ -78,7 +78,7 @@ def to_s private def data - call("UserGroup", "get_group_info", "groupName" => @name).xpath("//spdir:Group", NS).first + call("UserGroup", "GetGroupInfo", "groupName" => @name).xpath("//spdir:Group", NS).first end cache :data diff --git a/lib/activesp/item.rb b/lib/activesp/item.rb index 0830a13..54d1391 100644 --- a/lib/activesp/item.rb +++ b/lib/activesp/item.rb @@ -113,7 +113,7 @@ def key # @return [Array] def attachment_urls @list.when_list do - result = call("Lists", "get_attachment_collection", "listName" => @list.id, "listItemID" => @id) + result = call("Lists", "GetAttachmentCollection", "listName" => @list.id, "listItemID" => @id) return result.xpath("//sp:Attachment", NS).map { |att| att.text } end @list.when_document_library { raise TypeError, "a document library does not support attachments" } @@ -132,7 +132,7 @@ def add_attachment(parameters = {}) parameters = parameters.dup content = parameters.delete(:content) or raise ArgumentError, "Specify the content in the :content parameter" file_name = parameters.delete(:file_name) or raise ArgumentError, "Specify the file name in the :file_name parameter" - result = call("Lists", "add_attachment", "listName" => @list.ID, "listItemID" => self.ID, "fileName" => file_name, "attachment" => Base64.encode64(content.to_s)) + result = call("Lists", "AddAttachment", "listName" => @list.ID, "listItemID" => self.ID, "fileName" => file_name, "attachment" => Base64.encode64(content.to_s)) add_result = result.xpath("//sp:AddAttachmentResult", NS).first if add_result clear_cache_for(:attachment_urls) @@ -182,7 +182,7 @@ def content_type cache :content_type # def versions - # call("Versions", "get_versions", "fileName" => attributes["ServerUrl"]) + # call("Versions", "GetVersions", "fileName" => attributes["ServerUrl"]) # end # See {Base#save} @@ -195,7 +195,7 @@ def save def check_out @list.when_list { raise TypeError, "cannot check out list items; they would disappear" } @list.raise_on_unknown_type - result = call("Lists", "check_out_file", "pageUrl" => absolute_url, "checkoutToLocal" => false) + result = call("Lists", "CheckOutFile", "pageUrl" => absolute_url, "checkoutToLocal" => false) checkout_result = result.xpath("//sp:CheckOutFileResult", NS).first.text if checkout_result == "true" self @@ -215,7 +215,7 @@ def check_in(options = {}) if type == :minor && !@list.attribute("EnableMinorVersion") raise TypeError, "this list does not support minor versions" end - result = call("Lists", "check_in_file", "pageUrl" => absolute_url, "comment" => comment, "CheckinType" => checkin_type) + result = call("Lists", "CheckInFile", "pageUrl" => absolute_url, "comment" => comment, "CheckinType" => checkin_type) checkin_result = result.xpath("//sp:CheckInFileResult", NS).first.text if checkin_result == "true" self @@ -230,7 +230,7 @@ def check_in(options = {}) def cancel_checkout @list.when_list { raise TypeError, "cannot undo check-out for list items because you can't check them out" } @list.raise_on_unknown_type - result = call("Lists", "undo_check_out", "pageUrl" => absolute_url) + result = call("Lists", "UndoCheckOut", "pageUrl" => absolute_url) cancel_result = result.xpath("//sp:UndoCheckOutResult", NS).first.text if cancel_result == "true" self @@ -256,7 +256,7 @@ def destroy end end end - result = call("Lists", "update_list_items", "listName" => @list.id, "updates" => updates) + result = call("Lists", "UpdateListItems", "listName" => @list.id, "updates" => updates) create_result = result.xpath("//sp:Result", NS).first error_code = create_result.xpath("./sp:ErrorCode", NS).first.text.to_i(0) if error_code == 0 @@ -298,6 +298,7 @@ def raw_attributes @list.__each_item(query_options, "query" => query) do |attributes| return attributes end + raise ArgumentError, "Not found" end cache :raw_attributes @@ -338,7 +339,7 @@ def update_attributes_internal(attributes) xml.Field("1", "Name" => "FSObjType") if is_folder? end end - result = call("Lists", "update_list_items", "listName" => @list.id, "updates" => updates) + result = call("Lists", "UpdateListItems", "listName" => @list.id, "updates" => updates) create_result = result.xpath("//sp:Result", NS).first error_code = create_result.xpath("./sp:ErrorCode", NS).first.text.to_i(0) if error_code == 0 @@ -346,9 +347,15 @@ def update_attributes_internal(attributes) @attributes_before_type_cast = clean_item_attributes(row.attributes) reload else - message = create_result.xpath("./sp:ErrorText", NS).first - message &&= message.text - raise "cannot update item, error code = #{error_code}, error description = #{message}" + if error_code == 0x80004005 + raise ActiveSP::AccessDenied, "access denied" + elsif error_code == 0x80070005 + raise ActiveSP::PermissionDenied, "permission denied" + else + message = create_result.xpath("./sp:ErrorText", NS).first + message &&= message.text + raise "cannot update item, error code = #{error_code}, error description = #{message}" + end end end diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 16665a4..b1cdd51 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -195,9 +195,9 @@ def changes_since_token(token, options = {}) view_fields = Builder::XmlMarkup.new.ViewFields end if token - result = call("Lists", "get_list_item_changes_since_token", "listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields) + result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields) else - result = call("Lists", "get_list_item_changes_since_token", "listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields) + result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields) end updates = [] result.xpath("//z:row", NS).each do |row| @@ -235,7 +235,7 @@ def field(id) end def content_types - result = call("Lists", "get_list_content_types", "listName" => @id) + result = call("Lists", "GetListContentTypes", "listName" => @id) result.xpath("//sp:ContentType", NS).map do |content_type| ContentType.new(@site, self, content_type["ID"], content_type["Name"], content_type["Description"], content_type["Version"], content_type["Group"]) end @@ -291,10 +291,10 @@ def __each_item(query_options, query) get_list_items("", query_options, query) do |attributes| yield attributes end - rescue Savon::SOAPFault => e + rescue Savon::SOAP::Fault => e # This is where it gets ugly... Apparently there is a limit to the number of columns # you can retrieve with this operation. Joy! - if e.message[/lookup column threshold/] + if /lookup column threshold/ === e.error_string fields = self.fields.map { |f| f.Name } split_factor = 2 begin @@ -318,8 +318,8 @@ def __each_item(query_options, query) end yield attrs end - rescue Savon::SOAPFault => e - if e.message[/lookup column threshold/] + rescue Savon::SOAP::Fault => e + if /lookup column threshold/ === e.error_string split_factor += 1 retry else @@ -338,7 +338,7 @@ def ==(object) private def data1 - call("Lists", "get_list", "listName" => @id).xpath("//sp:List", NS).first + call("Lists", "GetList", "listName" => @id).xpath("//sp:List", NS).first end cache :data1 @@ -348,7 +348,7 @@ def attributes_before_type_cast1 cache :attributes_before_type_cast1 def data2 - call("SiteData", "get_list", "strListName" => @id) + call("SiteData", "GetList", "strListName" => @id) end cache :data2 @@ -434,7 +434,7 @@ def internal_attribute_types end def permissions - result = call("Permissions", "get_permission_collection", "objectName" => @id, "objectType" => "List") + result = call("Permissions", "GetPermissionCollection", "objectName" => @id, "objectType" => "List") rootsite = @site.rootsite result.xpath("//spdir:Permission", NS).map do |row| accessor = row["MemberIsUser"][/true/i] ? User.new(rootsite, row["UserLogin"]) : Group.new(rootsite, row["GroupName"]) @@ -444,7 +444,7 @@ def permissions cache :permissions, :dup => :always def get_list_items(view_fields, query_options, query) - result = call("Lists", "get_list_items", { "listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options }.merge(query)) + result = call("Lists", "GetListItems", { "listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options }.merge(query)) result.xpath("//z:row", NS).each do |row| yield clean_item_attributes(row.attributes) end @@ -477,7 +477,7 @@ def create_library_document(parameters) attributes = untype_cast_attributes(@site, self, fields_by_name, parameters) fields = construct_xml_for_copy_into_items(fields_by_name, attributes) source_url = escape_xml(file_name) - result = call("Copy", "copy_into_items", "DestinationUrls" => destination_urls, "Stream" => Base64.encode64(content.to_s), "SourceUrl" => source_url, "Fields" => fields) + result = call("Copy", "CopyIntoItems", "DestinationUrls" => destination_urls, "Stream" => Base64.encode64(content.to_s), "SourceUrl" => source_url, "Fields" => fields) copy_result = result.xpath("//sp:CopyResult", NS).first error_code = copy_result["ErrorCode"] if error_code != "Success" @@ -506,7 +506,7 @@ def create_list_item(parameters) end end end - result = call("Lists", "update_list_items", "listName" => self.id, "updates" => updates) + result = call("Lists", "UpdateListItems", "listName" => self.id, "updates" => updates) create_result = result.xpath("//sp:Result", NS).first error_text = create_result.xpath("./sp:ErrorText", NS).first if !error_text diff --git a/lib/activesp/role.rb b/lib/activesp/role.rb index 81e7cc9..1f5e201 100644 --- a/lib/activesp/role.rb +++ b/lib/activesp/role.rb @@ -47,7 +47,7 @@ def key # Returns the list of users in this role # @return [User] def users - call("UserGroup", "get_user_collection_from_role", "roleName" => @name).xpath("//spdir:User", NS).map do |row| + call("UserGroup", "GetUserCollectionFromRole", "roleName" => @name).xpath("//spdir:User", NS).map do |row| attributes = clean_attributes(row.attributes) User.new(@site, attributes["LoginName"]) end @@ -57,7 +57,7 @@ def users # Returns the list of groups in this role # @return [Group] def groups - call("UserGroup", "get_group_collection_from_role", "roleName" => @name).xpath("//spdir:Group", NS).map do |row| + call("UserGroup", "GetGroupCollectionFromRole", "roleName" => @name).xpath("//spdir:Group", NS).map do |row| attributes = clean_attributes(row.attributes) Group.new(@site, attributes["Name"]) end @@ -88,7 +88,7 @@ def to_s private def data - call("UserGroup", "get_role_info", "roleName" => @name).xpath("//spdir:Role", NS).first + call("UserGroup", "GetRoleInfo", "roleName" => @name).xpath("//spdir:Role", NS).first end cache :data diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index 7b7c05e..c32a03c 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -74,13 +74,13 @@ def is_root_site? # See {Base#key} # @return [String] def key # This documentation is not ideal. The ideal doesn't work out of the box - encode_key("S", [@url[@connection.root_url.length + 1..-1], @depth]) + encode_key("S", [@url[@connection.root_url.sub(/\/\z/, "").length + 1..-1], @depth]) end # Returns the list of sites below this site. Does not recurse # @return [Array] def sites - result = call("Webs", "get_web_collection") + result = call("Webs", "GetWebCollection") result.xpath("//sp:Web", NS).map { |web| Site.new(connection, web["Url"].to_s, @depth + 1) } end cache :sites, :dup => :always @@ -90,17 +90,17 @@ def sites # @param [String] name The name if the site # @return [Site] def site(name) - result = call("Webs", "get_web", "webUrl" => ::File.join(@url, name)) + result = call("Webs", "GetWeb", "webUrl" => ::File.join(@url, name)) Site.new(connection, result.xpath("//sp:Web", NS).first["Url"].to_s, @depth + 1) - rescue Savon::SOAPFault + rescue Savon::SOAP::Fault nil end # Returns the list if lists in this sute. Does not recurse # @return [Array] def lists - result1 = call("Lists", "get_list_collection") - result2 = call("SiteData", "get_list_collection") + result1 = call("Lists", "GetListCollection") + result2 = call("SiteData", "GetListCollection") result2_by_id = {} result2.xpath("//sp:_sList", NS).each do |element| data = {} @@ -134,7 +134,7 @@ def /(name) # containing sites as they are automatically inherited # @return [Array] def content_types - result = call("Webs", "get_content_types", "listName" => @id) + result = call("Webs", "GetContentTypes", "listName" => @id) result.xpath("//sp:ContentType", NS).map do |content_type| supersite && supersite.content_type(content_type["ID"]) || ContentType.new(self, nil, content_type["ID"], content_type["Name"], content_type["Description"], content_type["Version"], content_type["Group"]) end @@ -161,7 +161,7 @@ def permission_set # Returns the list of fields for this site. This includes fields inherited from containing sites # @return [Array] def fields - call("Webs", "get_columns").xpath("//sp:Field", NS).map do |field| + call("Webs", "GetColumns").xpath("//sp:Field", NS).map do |field| attributes = clean_attributes(field.attributes) supersite && supersite.field(attributes["ID"].downcase) || Field.new(self, attributes["ID"].downcase, attributes["StaticName"], attributes["Type"], nil, attributes) if attributes["ID"] && attributes["StaticName"] end.compact @@ -189,7 +189,7 @@ def save def accessible? data true - rescue Savon::HTTPError + rescue Savon::HTTP::Error false end @@ -218,10 +218,10 @@ def service(name) def data # Looks like you can't call this as a non-admin. To investigate further - call("SiteData", "get_web") - rescue Savon::HTTPError + call("SiteData", "GetWeb") + rescue Savon::HTTP::Error # This can fail when you don't have access to this site - call("Webs", "get_web", "webUrl" => ".") + call("Webs", "GetWeb", "webUrl" => ".") end cache :data @@ -265,7 +265,7 @@ def internal_attribute_types end def permissions - result = call("Permissions", "get_permission_collection", "objectName" => ::File.basename(@url), "objectType" => "Web") + result = call("Permissions", "GetPermissionCollection", "objectName" => ::File.basename(@url), "objectType" => "Web") result.xpath("//spdir:Permission", NS).map do |row| accessor = row["MemberIsUser"][/true/i] ? User.new(rootsite, row["UserLogin"]) : Group.new(rootsite, row["GroupName"]) { :mask => Integer(row["Mask"]), :accessor => accessor } @@ -278,16 +278,23 @@ class Service def initialize(site, name) @site, @name = site, name - @client = Savon::Client.new(::File.join(URI.escape(site.url), "_vti_bin", name + ".asmx?WSDL")) - if site.connection.login - case site.connection.auth_type - when :ntlm - @client.request.ntlm_auth(site.connection.login, site.connection.password) - when :basic - @client.request.basic_auth(site.connection.login, site.connection.password) - else - raise ArgumentError, "Unknown authentication type #{site.connection.auth_type.inspect}" + @client = Savon::Client.new do |wsdl, http| + wsdl.document = ::File.join(URI.escape(site.url), "_vti_bin", name + ".asmx?WSDL") + if site.connection.login + case site.connection.auth_type + when :ntlm + http.auth.ntlm(site.connection.login, site.connection.password) + when :basic + http.auth.basic(site.connection.login, site.connection.password) + when :digest + http.auth.digest(site.connection.login, site.connection.password) + when :gss_negotiate + http.auth.gssnegotiate(site.connection.login, site.connection.password) + else + raise ArgumentError, "Unknown authentication type #{site.connection.auth_type.inspect}" + end end + end end @@ -296,15 +303,19 @@ def call(m, *args) if Hash === args[-1] body = args.pop end - @client.send(m, *args) do |soap| + @client.request(:wsdl, m.snakecase, *args) do |soap| if body - soap.body = body.inject({}) { |h, (k, v)| h["wsdl:#{k}"] = v ; h } + soap.body = body.map do |k, v| + Builder::XmlMarkup.new.wsdl(k.to_sym) { |e| e << v } + end.join end yield soap if block_given? end - rescue Savon::SOAPFault => e + rescue Savon::SOAP::Fault => e if e.error_code == 0x80004005 - raise AccessDenied, "access denied" + raise ActiveSP::AccessDenied, "access denied" + elsif e.error_code == 0x80070005 + raise ActiveSP::PermissionDenied, "permission denied" else raise e end diff --git a/lib/activesp/user.rb b/lib/activesp/user.rb index 0e6e251..2600860 100644 --- a/lib/activesp/user.rb +++ b/lib/activesp/user.rb @@ -65,7 +65,7 @@ def to_s private def data - call("UserGroup", "get_user_info", "userLoginName" => @login_name).xpath("//spdir:User", NS).first + call("UserGroup", "GetUserInfo", "userLoginName" => @login_name).xpath("//spdir:User", NS).first end cache :data From 2bd789c33498816e9feff383e6b2c4b285001c5a Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 10 Jun 2011 11:29:33 +0200 Subject: [PATCH 02/32] Fix for empty params --- lib/activesp/site.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index c32a03c..9c8bc85 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -306,7 +306,7 @@ def call(m, *args) @client.request(:wsdl, m.snakecase, *args) do |soap| if body soap.body = body.map do |k, v| - Builder::XmlMarkup.new.wsdl(k.to_sym) { |e| e << v } + Builder::XmlMarkup.new.wsdl(k.to_sym) { |e| e << v.to_s } end.join end yield soap if block_given? From fdca9180e38603b4cba94cc35393043dcd178eea Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 10 Jun 2011 12:10:55 +0200 Subject: [PATCH 03/32] Also use the new authentication stuff for fetch and head --- lib/activesp/connection.rb | 96 +++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 9566edf..e05acb1 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -160,24 +160,40 @@ def fetch(url) u = URL(@root_url) [u.host, u.port] end - Net::HTTP.start(*@open_params) do |http| - request = Net::HTTP::Get.new(URL(url).full_path.gsub(/ /, "%20")) - if @login - case auth_type - when :ntlm - request.ntlm_auth(@login, @password) - when :basic - request.basic_auth(@login, @password) - else - raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" - end + # Net::HTTP.start(*@open_params) do |http| + # request = Net::HTTP::Get.new(URL(url).full_path.gsub(/ /, "%20")) + # if @login + # case auth_type + # when :ntlm + # request.ntlm_auth(@login, @password) + # when :basic + # request.basic_auth(@login, @password) + # else + # raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" + # end + # end + # response = http.request(request) + # # if Net::HTTPFound === response + # # response = fetch(response["location"]) + # # end + # # response + # end + request = HTTPI::Request.new("http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}") + if login + case auth_type + when :ntlm + request.auth.ntlm(login, password) + when :basic + request.auth.basic(login, password) + when :digest + request.auth.digest(login, password) + when :gss_negotiate + request.auth.gssnegotiate(login, password) + else + raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" end - response = http.request(request) - # if Net::HTTPFound === response - # response = fetch(response["location"]) - # end - # response end + HTTPI.get(request) end def head(url) @@ -186,24 +202,40 @@ def head(url) u = URL(@root_url) [u.host, u.port] end - Net::HTTP.start(*@open_params) do |http| - request = Net::HTTP::Head.new(URL(url).full_path.gsub(/ /, "%20")) - if @login - case auth_type - when :ntlm - request.ntlm_auth(@login, @password) - when :basic - request.basic_auth(@login, @password) - else - raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" - end + # Net::HTTP.start(*@open_params) do |http| + # request = Net::HTTP::Head.new(URL(url).full_path.gsub(/ /, "%20")) + # if @login + # case auth_type + # when :ntlm + # request.ntlm_auth(@login, @password) + # when :basic + # request.basic_auth(@login, @password) + # else + # raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" + # end + # end + # response = http.request(request) + # # if Net::HTTPFound === response + # # response = fetch(response["location"]) + # # end + # # response + # end + request = HTTPI::Request.new("http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}") + if login + case auth_type + when :ntlm + request.auth.ntlm(login, password) + when :basic + request.auth.basic(login, password) + when :digest + request.auth.digest(login, password) + when :gss_negotiate + request.auth.gssnegotiate(login, password) + else + raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" end - response = http.request(request) - # if Net::HTTPFound === response - # response = fetch(response["location"]) - # end - # response end + HTTPI.head(request).headers end end From c70e697e69d6963a972daf2a170b4697eee944a9 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 10 Jun 2011 16:07:55 +0200 Subject: [PATCH 04/32] Add support for ThreadIndex --- lib/activesp/list.rb | 2 +- lib/activesp/util.rb | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index b1cdd51..ac5271e 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -520,7 +520,7 @@ def create_list_item(parameters) else # Make it look like the error came from soap # Alternatively we could wrap all the soap faults maybe - raise Savon::SOAPFault.new(error_text.text.to_s, error_code) + raise ArgumentError.new(error_text.text.to_s) end end end diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 14c95f3..29dc524 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -101,7 +101,8 @@ def type_cast_attributes(site, list, fields, attributes) else v = (0...(d.length / 4)).map { |i| d[4 * i + 2] } end - + when "ThreadIndex" + else # raise NotImplementedError, "don't know type #{field.type.inspect} for #{k}=#{v.inspect}" # Note: can't print self if it needs the attributes to be loaded, so just display the class @@ -218,6 +219,12 @@ def type_check_attribute(field, value) end when "ContentTypeId" value + when "ThreadIndex" + if /\A0x([A-F0-9]+)\z/ === value + value + else + raise ArgumentError, "wrong value for #{field.Name} attribute" + end else raise "not yet #{field.Name}:#{field.internal_type}" end @@ -262,6 +269,7 @@ def untype_cast_attributes(site, list, fields, attributes) when "LookupMulti" v = v.map { |i| i.ID }.join(";#;#") when "ContentTypeId" + when "ThreadIndex" else raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" end From 93b7ca700d654b5519b611542dfa769f6a426f02 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Wed, 15 Jun 2011 17:55:46 +0200 Subject: [PATCH 05/32] Normalize the keys of lists (sometimes returned in lowercase) --- lib/activesp/list.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index ac5271e..02a5684 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -42,7 +42,7 @@ class List < Base persistent { |site, id, *a| [site.connection, [:list, id]] } # @private def initialize(site, id, title = nil, attributes_before_type_cast1 = nil, attributes_before_type_cast2 = nil) - @site, @id = site, id + @site, @id = site, id.upcase @Title = title if title # This testing for emptiness of RootFolder is necessary because it is empty # in bulk calls. From c8341ea320dc41e256df8d42f7f76e8d2ae23c7b Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Thu, 16 Jun 2011 15:20:24 +0200 Subject: [PATCH 06/32] Use a specific error when an item is not found --- lib/activesp/errors.rb | 3 +++ lib/activesp/item.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/activesp/errors.rb b/lib/activesp/errors.rb index e886ea7..9a199fd 100644 --- a/lib/activesp/errors.rb +++ b/lib/activesp/errors.rb @@ -1,5 +1,8 @@ module ActiveSP + class NotFound < Exception + end + class AccessDenied < Exception end diff --git a/lib/activesp/item.rb b/lib/activesp/item.rb index 54d1391..5dda0f7 100644 --- a/lib/activesp/item.rb +++ b/lib/activesp/item.rb @@ -298,7 +298,7 @@ def raw_attributes @list.__each_item(query_options, "query" => query) do |attributes| return attributes end - raise ArgumentError, "Not found" + raise ActiveSP::NotFound, "Not found" end cache :raw_attributes From b682b92cbd4faed533e59e8a14ed006c1e6697b2 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 17 Jun 2011 17:34:57 +0200 Subject: [PATCH 07/32] A number of fixes Added methods to override read-onliness of attributes --- lib/activesp/base.rb | 18 ++++++++++++++--- lib/activesp/content_type.rb | 2 +- lib/activesp/field.rb | 2 +- lib/activesp/folder.rb | 14 +++++++++++++ lib/activesp/group.rb | 2 +- lib/activesp/item.rb | 19 ++++++++++++++++-- lib/activesp/list.rb | 38 ++++++++++++++++++++++++++++-------- lib/activesp/role.rb | 2 +- lib/activesp/site.rb | 7 ++++++- lib/activesp/user.rb | 2 +- lib/activesp/util.rb | 32 +++++++++++++++++++++++------- 11 files changed, 112 insertions(+), 26 deletions(-) diff --git a/lib/activesp/base.rb b/lib/activesp/base.rb index 0be8700..c02ddd6 100644 --- a/lib/activesp/base.rb +++ b/lib/activesp/base.rb @@ -85,9 +85,11 @@ def attribute_type(name) # @return [Integer, Float, String, Time, Boolean, Base] The assigned value # @raise [ArgumentError] Raised when this object does not have an attribute by the given name or if the attribute by the given name is read-only def set_attribute(name, value) - has_attribute?(name) and field = attribute_type(name) and internal_attribute_types[name] or raise ArgumentError, "#{self} has no field by the name #{name}" - !field.ReadOnly or raise ArgumentError, "field #{name} of #{self} is read-only" - current_attributes[name] = type_check_attribute(field, value) + set_attribute_internal(name, value, false) + end + + def set_attribute!(name, value) + set_attribute_internal(name, value, true) end # Provides convenient getters and setters for attributes. Note that no name mangling @@ -134,6 +136,16 @@ def changed_attributes changes end + def set_attribute_internal(name, value, override_restrictions) + has_attribute?(name) and field = attribute_type_internal(name, override_restrictions) or raise ArgumentError, "#{self} has no field by the name #{name}" + override_restrictions or !field.ReadOnly or raise ArgumentError, "field #{name} of #{self} is read-only" + current_attributes[name] = type_check_attribute(field, value, override_restrictions) + end + + def attribute_type_internal(name, override_restrictions) + attribute_type(name) + end + end end diff --git a/lib/activesp/content_type.rb b/lib/activesp/content_type.rb index a4390a3..13de926 100644 --- a/lib/activesp/content_type.rb +++ b/lib/activesp/content_type.rb @@ -106,7 +106,7 @@ def fields_by_name # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, false) end # @private diff --git a/lib/activesp/field.rb b/lib/activesp/field.rb index 3cb4cd8..4f6c284 100644 --- a/lib/activesp/field.rb +++ b/lib/activesp/field.rb @@ -78,7 +78,7 @@ def ReadOnly # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, false) end # @private diff --git a/lib/activesp/folder.rb b/lib/activesp/folder.rb index dec9c9d..92c9b4a 100644 --- a/lib/activesp/folder.rb +++ b/lib/activesp/folder.rb @@ -52,12 +52,19 @@ def each_folder(options = {}, &blk) def create(parameters = {}) @object.create_folder(parameters) end + def create!(parameters = {}) + @object.create_folder!(parameters) + end end def create_folder(parameters = {}) @list.create_folder(parameters.merge(:folder => absolute_url)) end + def create_folder!(parameters = {}) + @list.create_folder!(parameters.merge(:folder => absolute_url)) + end + def each_document(options = {}, &blk) @list.each_document(options.merge(:folder => self), &blk) end @@ -65,12 +72,19 @@ def each_document(options = {}, &blk) def create(parameters = {}) @object.create_document(parameters) end + def create!(parameters = {}) + @object.create_document!(parameters) + end end def create_document(parameters = {}) @list.create_document(parameters.merge(:folder => absolute_url, :folder_object => self)) end + def create_document!(parameters = {}) + @list.create_document!(parameters.merge(:folder => absolute_url, :folder_object => self)) + end + # Returns the item with the given name # @param [String] name # @return [Item] diff --git a/lib/activesp/group.rb b/lib/activesp/group.rb index 0448d38..ba94132 100644 --- a/lib/activesp/group.rb +++ b/lib/activesp/group.rb @@ -64,7 +64,7 @@ def is_role? # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, false) end # @private diff --git a/lib/activesp/item.rb b/lib/activesp/item.rb index 5dda0f7..07e2794 100644 --- a/lib/activesp/item.rb +++ b/lib/activesp/item.rb @@ -188,7 +188,7 @@ def content_type # See {Base#save} # @return [self] def save - update_attributes_internal(untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes)) + update_attributes_internal(untype_cast_attributes(@site, nil, @list.fields_by_name, changed_attributes, true)) self end @@ -246,6 +246,13 @@ def update_attributes(attributes) save end + def update_attributes!(attributes) + attributes.each do |k, v| + set_attribute!(k, v) + end + save + end + def destroy updates = Builder::XmlMarkup.new.Batch("OnError" => "Continue", "ListVersion" => 1) do |xml| xml.Method("ID" => 1, "Cmd" => "Delete") do @@ -331,7 +338,7 @@ def update_attributes_internal(attributes) updates = Builder::XmlMarkup.new.Batch("OnError" => "Continue", "ListVersion" => @list.attribute("Version")) do |xml| xml.Method("ID" => 1, "Cmd" => "Update") do xml.Field(self.ID, "Name" => "ID") - construct_xml_for_update_list_items(xml, @list.fields_by_name, attributes) + construct_xml_for_update_list_items(xml, @list, @list.fields_by_name, attributes) if file_ref xml.Field(base_name, "Name" => "BaseName") xml.Field(file_ref, "Name" => "FileRef") @@ -359,6 +366,14 @@ def update_attributes_internal(attributes) end end + def attribute_type_internal(name, override_restrictions) + if override_restrictions + @list.fields_by_name[name] + else + attribute_type(name) + end + end + end end diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 02a5684..a5da01d 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -131,6 +131,9 @@ def each_document(parameters = {}, &blk) def create(parameters = {}) @object.create_document(parameters) end + def create!(parameters = {}) + @object.create_document!(parameters) + end end def each_folder(parameters = {}, &blk) @@ -148,6 +151,9 @@ def each_folder(parameters = {}, &blk) def create(parameters = {}) @object.create_folder(parameters) end + def create!(parameters = {}) + @object.create_folder!(parameters) + end end # Returns the item with the given name or nil if there is no item with the given name @@ -176,12 +182,20 @@ def create_document(parameters = {}) raise_on_unknown_type end + def create_document!(parameters = {}) + create_document(parameters.merge(:override_restrictions => true)) + end + def create_folder(parameters = {}) name = parameters.delete("FileLeafRef") or raise ArgumentError, "Specify the folder name in the 'FileLeafRef' parameter" create_list_item(parameters.merge(:folder_name => name)) end + def create_folder!(parameters = {}) + create_folder(parameters.merge(:override_restrictions => true)) + end + def changes_since_token(token, options = {}) options = options.dup no_preload = options.delete(:no_preload) @@ -242,6 +256,11 @@ def content_types end cache :content_types, :dup => :always + def content_types_by_name + content_types.inject({}) { |h, t| h[t.Name] = t ; h } + end + cache :content_types_by_name, :dup => :always + # @private def content_type(id) content_types.find { |t| t.id == id } @@ -259,7 +278,7 @@ def permission_set # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, override_restrictions) end # @private @@ -467,14 +486,15 @@ def create_library_document(parameters) folder = parameters.delete(:folder) folder_object = parameters.delete(:folder_object) overwrite = parameters.delete(:overwrite) + override_restrictions = parameters.delete(:override_restrictions) file_name = parameters.delete("FileLeafRef") or raise ArgumentError, "Specify the file name in the 'FileLeafRef' parameter" if !overwrite object = __item(file_name, :folder => folder_object) raise ActiveSP::AlreadyExists.new("document with file name #{file_name.inspect} already exists") { object } if object end destination_urls = Builder::XmlMarkup.new.wsdl(:string, URI.escape(::File.join(folder || url, file_name))) - parameters = type_check_attributes_for_creation(fields_by_name, parameters) - attributes = untype_cast_attributes(@site, self, fields_by_name, parameters) + parameters = type_check_attributes_for_creation(fields_by_name, parameters, override_restrictions) + attributes = untype_cast_attributes(@site, self, fields_by_name, parameters, override_restrictions) fields = construct_xml_for_copy_into_items(fields_by_name, attributes) source_url = escape_xml(file_name) result = call("Copy", "CopyIntoItems", "DestinationUrls" => destination_urls, "Stream" => Base64.encode64(content.to_s), "SourceUrl" => source_url, "Fields" => fields) @@ -492,17 +512,19 @@ def create_list_item(parameters) folder = parameters.delete(:folder) folder_object = parameters.delete(:folder_object) folder_name = parameters.delete(:folder_name) - parameters = type_check_attributes_for_creation(fields_by_name, parameters) - attributes = untype_cast_attributes(@site, self, fields_by_name, parameters) - updates = Builder::XmlMarkup.new.Batch("OnError" => "Continue", "ListVersion" => 1) do |xml| + override_restrictions = parameters.delete(:override_restrictions) + parameters = type_check_attributes_for_creation(fields_by_name, parameters, override_restrictions) + attributes = untype_cast_attributes(@site, self, fields_by_name, parameters, override_restrictions) + folder_attributes = !folder_name && folder ? { "RootFolder" => folder } : {} + updates = Builder::XmlMarkup.new.Batch(folder_attributes.merge("OnError" => "Continue")) do |xml| xml.Method("ID" => 1, "Cmd" => "New") do xml.Field("New", "Name" => "ID") - construct_xml_for_update_list_items(xml, fields_by_name, attributes) + construct_xml_for_update_list_items(xml, self, fields_by_name, attributes) if folder_name xml.Field(::File.join(folder || url, folder_name), "Name" => "FileRef") xml.Field(1, "Name" => "FSObjType") else - xml.Field(::File.join(folder, Time.now.strftime("%Y%m%d%H%M%S-#{rand(16**3).to_s(16)}")), "Name" => "FileRef") if folder + xml.Field(0, "Name" => "FSObjType") end end end diff --git a/lib/activesp/role.rb b/lib/activesp/role.rb index 1f5e201..aece959 100644 --- a/lib/activesp/role.rb +++ b/lib/activesp/role.rb @@ -74,7 +74,7 @@ def is_role? # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, false) end # @private diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index 9c8bc85..f9d5215 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -141,6 +141,11 @@ def content_types end cache :content_types, :dup => :always + def content_types_by_name + content_types.inject({}) { |h, t| h[t.Name] = t ; h } + end + cache :content_types_by_name, :dup => :always + # @private def content_type(id) content_types.find { |t| t.id == id } @@ -183,7 +188,7 @@ def field(id) # See {Base#save} # @return [void] def save - p untype_cast_attributes(self, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(self, nil, internal_attribute_types, changed_attributes, false) end def accessible? diff --git a/lib/activesp/user.rb b/lib/activesp/user.rb index 2600860..bb9a498 100644 --- a/lib/activesp/user.rb +++ b/lib/activesp/user.rb @@ -51,7 +51,7 @@ def key # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes) + p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, false) end # @private diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 29dc524..988ee77 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -150,7 +150,7 @@ def create_user_or_group_by_id(site, id) end end - def type_check_attribute(field, value) + def type_check_attribute(field, value, override_restrictions) case field.internal_type when "Text", "File", "Note", "URL", "Choice" value.to_s @@ -225,16 +225,22 @@ def type_check_attribute(field, value) else raise ArgumentError, "wrong value for #{field.Name} attribute" end + when "Computed" + if override_restrictions + value.to_s + else + raise "not yet #{field.Name}:#{field.internal_type}" + end else raise "not yet #{field.Name}:#{field.internal_type}" end end - def type_check_attributes_for_creation(fields, attributes) + def type_check_attributes_for_creation(fields, attributes, override_restrictions) attributes.inject({}) do |h, (k, v)| if field = fields[k] - if !field.ReadOnly || field.Name == "ContentType" - h[k] = type_check_attribute(field, v) + if override_restrictions || !field.ReadOnly || field.Name == "ContentType" + h[k] = type_check_attribute(field, v, override_restrictions) h else raise ArgumentError, "field #{field.Name} is read-only" @@ -245,7 +251,7 @@ def type_check_attributes_for_creation(fields, attributes) end end - def untype_cast_attributes(site, list, fields, attributes) + def untype_cast_attributes(site, list, fields, attributes, override_restrictions) attributes.inject({}) do |h, (k, v)| if field = fields[k] case field.internal_type @@ -270,6 +276,12 @@ def untype_cast_attributes(site, list, fields, attributes) v = v.map { |i| i.ID }.join(";#;#") when "ContentTypeId" when "ThreadIndex" + when "Computed" + if override_restrictions + v = v.to_s + else + raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" + end else raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" end @@ -295,10 +307,16 @@ def construct_xml_for_copy_into_items(fields, attributes) end.join("") end - def construct_xml_for_update_list_items(xml, fields, attributes) + def construct_xml_for_update_list_items(xml, list, fields, attributes) attributes.map do |k, v| field = fields[k] - xml.Field(v, "Name" => field.StaticName) + if field.StaticName == "ContentType" + type = list.content_types_by_name[v] + xml.Field(v, "Name" => field.StaticName) + xml.Field(type.ID, "Name" => "ContentTypeId") + else + xml.Field(v, "Name" => field.StaticName) + end end end From e05259b7ca82517776dd0d39e042b6b44811e524 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Wed, 22 Jun 2011 18:12:35 +0200 Subject: [PATCH 08/32] Add support for fetching the list of templates --- lib/activesp.rb | 1 + lib/activesp/root.rb | 12 +++++++ lib/activesp/template.rb | 75 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 lib/activesp/template.rb diff --git a/lib/activesp.rb b/lib/activesp.rb index 6c12fa3..a6c5fbb 100644 --- a/lib/activesp.rb +++ b/lib/activesp.rb @@ -53,3 +53,4 @@ module ActiveSP require 'activesp/role' require 'activesp/permission_set' require 'activesp/file' +require 'activesp/template' diff --git a/lib/activesp/root.rb b/lib/activesp/root.rb index 88b1679..c1ab3f9 100644 --- a/lib/activesp/root.rb +++ b/lib/activesp/root.rb @@ -90,6 +90,18 @@ def roles end cache :roles, :dup => :always + def templates + root.send(:call, "Sites", "GetSiteTemplates", "LCID" => 1033).xpath("//sp:Template", NS).map do |row| + attributes = clean_attributes(row.attributes) + ActiveSP::Template.new(root, attributes["Name"], attributes) + end + end + cache :templates, :dup => :always + + def template(name) + templates.find { |template| template.Name == name } + end + private def users_by_login diff --git a/lib/activesp/template.rb b/lib/activesp/template.rb new file mode 100644 index 0000000..863fc4b --- /dev/null +++ b/lib/activesp/template.rb @@ -0,0 +1,75 @@ +# Copyright (c) 2010 XAOP bvba +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +module ActiveSP + + class Template < Base + + extend Caching + extend PersistentCaching + include Util + + attr_reader :Name + + persistent { |connection, name, *a| [connection, [:template, name]] } + # @private + def initialize(connection, name, attributes_before_type_cast) + @connection, @Name, @attributes_before_type_cast = connection, name, attributes_before_type_cast + end + + # @private + def to_s + "#" + end + + # @private + alias inspect to_s + + private + + def original_attributes + @original_attributes ||= type_cast_attributes(@site, nil, internal_attribute_types, @attributes_before_type_cast) + end + + def internal_attribute_types + @@internal_attribute_types ||= { + "Description" => GhostField.new("Description", "Text", false, true), + "DisplayCategory" => GhostField.new("DisplayCategory", "Text", false, true), + "HasProvisionClass" => GhostField.new("HasProvisionClass", "Bool", false, true), + "ID" => GhostField.new("ID", "Integer", false, true), + "ImageUrl" => GhostField.new("ImageUrl", "Text", false, true), + "IsCustom" => GhostField.new("IsCustom", "Bool", false, true), + "IsHidden" => GhostField.new("IsHidden", "Bool", false, true), + "IsRootWebOnly" => GhostField.new("IsRootWebOnly", "Bool", false, true), + "IsSubWebOnly" => GhostField.new("IsSubWebOnly", "Bool", false, true), + "IsUnique" => GhostField.new("IsUnique", "Bool", false, true), + "Name" => GhostField.new("Name", "Text", false, true), + "Title" => GhostField.new("Title", "Text", false, true) + } + end + + end + +end From 2461880f6cacc9a7d64dbfe557b1d195559fa590 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 24 Jun 2011 13:52:33 +0200 Subject: [PATCH 09/32] Add some administrative stuff Some bug fixes --- lib/activesp.rb | 3 +- lib/activesp/connection.rb | 19 +++ lib/activesp/field.rb | 59 +++++--- lib/activesp/item.rb | 6 +- lib/activesp/list.rb | 65 +++++++-- lib/activesp/list_template.rb | 84 +++++++++++ lib/activesp/root.rb | 26 +++- lib/activesp/site.rb | 130 ++++++++++++++---- .../{template.rb => site_template.rb} | 6 +- lib/activesp/util.rb | 9 +- 10 files changed, 337 insertions(+), 70 deletions(-) create mode 100644 lib/activesp/list_template.rb rename lib/activesp/{template.rb => site_template.rb} (97%) diff --git a/lib/activesp.rb b/lib/activesp.rb index a6c5fbb..71d7b04 100644 --- a/lib/activesp.rb +++ b/lib/activesp.rb @@ -53,4 +53,5 @@ module ActiveSP require 'activesp/role' require 'activesp/permission_set' require 'activesp/file' -require 'activesp/template' +require 'activesp/site_template' +require 'activesp/list_template' diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index e05acb1..747a808 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -80,6 +80,16 @@ def setup_gssnegotiate_auth(request) end +# This is because setting the cookie causes problems on SP 2011 +class Savon::Client + +private + + def set_cookie(headers) + end + +end + module ActiveSP # This class is uses to configure the connection to a SharePoint repository. This is @@ -143,6 +153,15 @@ def find_by_key(key) else ActiveSP::ContentType.new(parent, nil, trail[1]) end + when ?M + case type[1] + when ?S + site_template(trail[0]) + when ?L + list_template(trail[0].to_i) + else + raise "not yet #{key.inspect}" + end else raise "not yet #{key.inspect}" end diff --git a/lib/activesp/field.rb b/lib/activesp/field.rb index 4f6c284..5164898 100644 --- a/lib/activesp/field.rb +++ b/lib/activesp/field.rb @@ -30,6 +30,7 @@ class Field < Base include InSite extend Caching include Util + extend Util # @private attr_reader :ID, :Name, :internal_type @@ -89,6 +90,15 @@ def to_s # @private alias inspect to_s + def self.check_attributes_for_creation(site, attributes) + name = attributes.delete("Name") or raise ArgumentError, "wrong type for Name attribute" + name = name.to_s + type = attributes.delete("internal_type") or raise ArgumentError, "wrong type for Name attribute" + %[DateTime XMLDateTime Computed Text Guid ContentTypeId URL Integer Counter Attachments ModStat Number Bool File Note User InternalUser UserMulti Choice MultiChoice Lookup LookupMulti ThreadIndex].include?(type) or raise ArgumentError, "illegal value for internal_type attribute" + attributes = type_check_attributes_for_creation(internal_attribute_types, attributes, false).merge("Name" => name, "Type" => type) + untype_cast_attributes(site, nil, internal_attribute_types, attributes, false) + end + private def list_for_lookup @@ -107,7 +117,7 @@ def original_attributes @original_attributes ||= type_cast_attributes(@site, nil, internal_attribute_types, @attributes_before_type_cast.merge("List" => list_for_lookup, "Type" => self.Type, "internal_type" => internal_type)) end - def internal_attribute_types + def self.internal_attribute_types @@internal_attribute_types ||= { "AllowDeletion" => GhostField.new("AllowDeletion", "Bool", false, true), "AppendOnly" => GhostField.new("AppendOnly", "Bool", false, true), @@ -116,23 +126,24 @@ def internal_attribute_types "CanToggleHidden" => GhostField.new("CanToggleHidden", "Bool", false, true), "ClassInfo" => GhostField.new("ClassInfo", "Text", false, true), "ColName" => GhostField.new("ColName", "Text", false, true), - "Description" => GhostField.new("Description", "Text", false, true), + "Description" => GhostField.new("Description", "Text", false, false), "Dir" => GhostField.new("Dir", "Text", false, true), "DisplaceOnUpgrade" => GhostField.new("DisplaceOnUpgrade", "Bool", false, true), "DisplayImage" => GhostField.new("DisplayImage", "Text", false, true), - "DisplayName" => GhostField.new("DisplayName", "Text", false, true), + "DisplayName" => GhostField.new("DisplayName", "Text", false, false), "DisplayNameSrcField" => GhostField.new("DisplayNameSrcField", "Text", false, true), "DisplaySize" => GhostField.new("DisplaySize", "Integer", false, true), "ExceptionImage" => GhostField.new("ExceptionImage", "Text", false, true), "FieldRef" => GhostField.new("FieldRef", "Text", false, true), "FillInChoice" => GhostField.new("FillInChoice", "Bool", false, true), - "Filterable" => GhostField.new("Filterable", "Bool", false, true), - "Format" => GhostField.new("Format", "Bool", false, true), - "FromBaseType" => GhostField.new("FromBaseType", "Bool", false, true), + "Filterable" => GhostField.new("Filterable", "Bool", false, false), + "FilterableNoRecurrence" => GhostField.new("FilterableNoRecurrence", "Bool", false, false), + "Format" => GhostField.new("Format", "Bool", false, false), + "FromBaseType" => GhostField.new("FromBaseType", "Bool", false, false), "Group" => GhostField.new("Group", "Text", false, true), "HeaderImage" => GhostField.new("HeaderImage", "Text", false, true), "Height" => GhostField.new("Height", "Integer", false, true), - "Hidden" => GhostField.new("Hidden", "Bool", false, true), + "Hidden" => GhostField.new("Hidden", "Bool", false, false), "ID" => GhostField.new("ID", "Text", false, true), "IMEMode" => GhostField.new("IMEMode", "Text", false, true), "internal_type" => GhostField.new("internal_type", "Text", false, true), @@ -140,15 +151,15 @@ def internal_attribute_types "JoinColName" => GhostField.new("JoinColName", "Text", false, true), "JoinRowOrdinal" => GhostField.new("JoinRowOrdinal", "Integer", false, true), "JoinType" => GhostField.new("JoinType", "Text", false, true), - "List" => GhostField.new("List", "ListReference", false, true), + "List" => GhostField.new("List", "ListReference", false, false), "Max" => GhostField.new("Max", "Integer", false, true), "MaxLength" => GhostField.new("MaxLength", "Integer", false, true), "Min" => GhostField.new("Min", "Integer", false, true), "Mult" => GhostField.new("Mult", "Bool", false, true), "Name" => GhostField.new("Name", "Text", false, true), - "Node" => GhostField.new("Node", "Text", false, true), + "Node" => GhostField.new("Node", "Text", false, false), "NoEditFormBreak" => GhostField.new("NoEditFormBreak", "Bool", false, true), - "NumLines" => GhostField.new("NumLines", "Text", false, true), + "NumLines" => GhostField.new("NumLines", "Text", false, false), "Percentage" => GhostField.new("Percentage", "Bool", false, true), "PIAttribute" => GhostField.new("PIAttribute", "Text", false, true), "PITarget" => GhostField.new("PITarget", "Text", false, true), @@ -156,20 +167,20 @@ def internal_attribute_types "PrimaryKey" => GhostField.new("PrimaryKey", "Bool", false, true), "PrimaryPIAttribute" => GhostField.new("PrimaryPIAttribute", "Text", false, true), "PrimaryPITarget" => GhostField.new("PrimaryPITarget", "Text", false, true), - "ReadOnly" => GhostField.new("ReadOnly", "Bool", false, true), + "ReadOnly" => GhostField.new("ReadOnly", "Bool", false, false), "ReadOnlyEnforced" => GhostField.new("ReadOnlyEnforced", "Bool", false, true), "RenderXMLUsingPattern" => GhostField.new("ReadOnly", "Bool", false, true), - "Required" => GhostField.new("Required", "Bool", false, true), + "Required" => GhostField.new("Required", "Bool", false, false), "RestrictedMode" => GhostField.new("RestrictedMode", "Bool", false, true), "RichText" => GhostField.new("RichText", "Bool", false, true), "RichTextMode" => GhostField.new("RichTextMode", "Text", false, true), "RowOrdinal" => GhostField.new("RowOrdinal", "Integer", false, true), - "Sealed" => GhostField.new("Sealed", "Bool", false, true), - "ShowInDisplayForm" => GhostField.new("ShowInDisplayForm", "Bool", false, true), - "ShowInListSettings" => GhostField.new("ShowInListSettings", "Bool", false, true), - "ShowInFileDlg" => GhostField.new("ShowInFileDlg", "Bool", false, true), + "Sealed" => GhostField.new("Sealed", "Bool", false, false), + "ShowInDisplayForm" => GhostField.new("ShowInDisplayForm", "Bool", false, false), + "ShowInListSettings" => GhostField.new("ShowInListSettings", "Bool", false, false), + "ShowInFileDlg" => GhostField.new("ShowInFileDlg", "Bool", false, false), "ShowInVersionHistory" => GhostField.new("ShowInVersionHistory", "Bool", false, true), - "Sortable" => GhostField.new("Sortable", "Bool", false, true), + "Sortable" => GhostField.new("Sortable", "Bool", false, false), "SourceID" => GhostField.new("SourceID", "Text", false, true), "StaticName" => GhostField.new("StaticName", "Text", false, true), "StorageTZ" => GhostField.new("StorageTZ", "Bool", false, true), @@ -177,9 +188,9 @@ def internal_attribute_types "Title" => GhostField.new("Title", "Text", false, true), "Type" => GhostField.new("Type", "Text", false, true), "SetAs" => GhostField.new("SetAs", "Text", false, true), - "ShowField" => GhostField.new("ShowField", "Text", false, true), - "ShowInEditForm" => GhostField.new("ShowInEditForm", "Bool", false, true), - "ShowInNewForm" => GhostField.new("ShowInNewForm", "Bool", false, true), + "ShowField" => GhostField.new("ShowField", "Text", false, false), + "ShowInEditForm" => GhostField.new("ShowInEditForm", "Bool", false, false), + "ShowInNewForm" => GhostField.new("ShowInNewForm", "Bool", false, false), "UnlimitedLengthInDocumentLibrary" => GhostField.new("UnlimitedLengthInDocumentLibrary", "Bool", false, true), "Version" => GhostField.new("Version", "Integer", false, true), "Width" => GhostField.new("Width", "Integer", false, true), @@ -188,6 +199,14 @@ def internal_attribute_types } end + def internal_attribute_types + self.class.internal_attribute_types + end + end end + +__END__ + +Reference of attributes for fields: http://msdn.microsoft.com/en-us/library/aa543225.aspx diff --git a/lib/activesp/item.rb b/lib/activesp/item.rb index 07e2794..919997a 100644 --- a/lib/activesp/item.rb +++ b/lib/activesp/item.rb @@ -341,7 +341,11 @@ def update_attributes_internal(attributes) construct_xml_for_update_list_items(xml, @list, @list.fields_by_name, attributes) if file_ref xml.Field(base_name, "Name" => "BaseName") - xml.Field(file_ref, "Name" => "FileRef") + xml.Field(file_ref, "Name" => "FileRef") + else + @list.when_document_library do + xml.Field(URI.unescape(original_attributes["EncodedAbsUrl"]), "Name" => "FileRef") + end end xml.Field("1", "Name" => "FSObjType") if is_folder? end diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index a5da01d..8d64496 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -248,6 +248,32 @@ def field(id) fields.find { |f| f.ID == id } end + def create_field(attributes) + # TODO: remove this in time + add_to_view = attributes.delete(:add_to_view) + parameters = ActiveSP::Field.check_attributes_for_creation(@site, attributes) + fields = Builder::XmlMarkup.new.Fields do |xml| + xml.Method({ "ID" => "1" }.merge(add_to_view ? { "AddToView" => add_to_view } : {})) do + xml.Field(parameters) + end + end + result = call("Lists", "UpdateList", "listName" => self.id, "newFields" => fields) + method = result.xpath("//sp:Method", NS).first + if error_text = method.xpath("./sp:ErrorText", NS).first + error_code = method.xpath("./sp:ErrorCode", NS).first.text + raise ArgumentError.new("#{error_code} : #{error_text.text.to_s}") + else + field = method.xpath("./sp:Field", NS).first + attributes = clean_attributes(field.attributes) + result = Field.new(self, attributes["ID"].downcase, attributes["StaticName"], attributes["Type"], @site.field(attributes["ID"].downcase), attributes) + if @fields + @fields << result + clear_cache_for(:fields_by_name) + end + result + end + end + def content_types result = call("Lists", "GetListContentTypes", "listName" => @id) result.xpath("//sp:ContentType", NS).map do |content_type| @@ -275,10 +301,18 @@ def permission_set end cache :permission_set + def update_attributes(attributes) + attributes.each do |k, v| + set_attribute(k, v) + end + save + end + # See {Base#save} # @return [void] def save - p untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, override_restrictions) + update_attributes_internal(untype_cast_attributes(@site, nil, internal_attribute_types, changed_attributes, true)) + self end # @private @@ -391,7 +425,7 @@ def internal_attribute_types @@internal_attribute_types ||= { "AllowAnonymousAccess" => GhostField.new("AllowAnonymousAccess", "Bool", false, true, "Allow Anonymous Access?"), "AllowDeletion" => GhostField.new("AllowDeletion", "Bool", false, true, "Allow Deletion?"), - "AllowMultiResponses" => GhostField.new("AllowMultiResponses", "Bool", false, true, "Allow Multiple Responses?"), + "AllowMultiResponses" => GhostField.new("AllowMultiResponses", "Bool", false, false, "Allow Multiple Responses?"), "AnonymousPermMask" => GhostField.new("AnonymousPermMask", "Integer", false, true, "Anonymous Permission Mask"), "AnonymousViewListItems" => GhostField.new("AnonymousViewListItems", "Bool", false, true, "Anonymous Can View List Items?"), "Author" => GhostField.new("Author", "InternalUser", false, true), @@ -400,22 +434,22 @@ def internal_attribute_types "Created" => GhostField.new("Created", "StandardDateTime", false, true, "Created"), "DefaultViewUrl" => GhostField.new("DefaultViewUrl", "Text", false, true, "Default View Url"), "Description" => GhostField.new("Description", "Text", false, false), - "Direction" => GhostField.new("Direction", "Text", false, true), + "Direction" => GhostField.new("Direction", "Text", false, false), "DocTemplateUrl" => GhostField.new("DocTemplateUrl", "Text", false, true, "Document Template URL"), "EmailAlias" => GhostField.new("EmailAlias", "Text", false, true, "Email Alias"), "EmailInsertsFolder" => GhostField.new("EmailInsertsFolder", "Text", false, true, "Email Inserts Folder"), - "EnableAssignedToEmail" => GhostField.new("EnableAssignedToEmail", "Bool", false, true, "Enable Assign to Email?"), - "EnableAttachments" => GhostField.new("EnableAttachments", "Bool", false, true, "Enable Attachments?"), + "EnableAssignedToEmail" => GhostField.new("EnableAssignedToEmail", "Bool", false, false, "Enable Assign to Email?"), + "EnableAttachments" => GhostField.new("EnableAttachments", "Bool", false, false, "Enable Attachments?"), "EnableMinorVersion" => GhostField.new("EnableMinorVersion", "Bool", false, true, "Enable Minor Versions?"), - "EnableModeration" => GhostField.new("EnableModeration", "Bool", false, true, "Enable Moderation?"), - "EnableVersioning" => GhostField.new("EnableVersioning", "Bool", false, true, "Enable Versioning?"), + "EnableModeration" => GhostField.new("EnableModeration", "Bool", false, false, "Enable Moderation?"), + "EnableVersioning" => GhostField.new("EnableVersioning", "Bool", false, false, "Enable Versioning?"), "EventSinkAssembly" => GhostField.new("EventSinkAssembly", "Text", false, true, "Event Sink Assembly"), "EventSinkClass" => GhostField.new("EventSinkClass", "Text", false, true, "Event Sink Class"), "EventSinkData" => GhostField.new("EventSinkData", "Text", false, true, "Event Sink Data"), "FeatureId" => GhostField.new("FeatureId", "Text", false, true, "Feature ID"), "Flags" => GhostField.new("Flags", "Integer", false, true), "HasUniqueScopes" => GhostField.new("HasUniqueScopes", "Bool", false, true, "Has Unique Scopes?"), - "Hidden" => GhostField.new("Hidden", "Bool", false, true), + "Hidden" => GhostField.new("Hidden", "Bool", false, false), "ID" => GhostField.new("ID", "Text", false, true), "ImageUrl" => GhostField.new("ImageUrl", "Text", false, true, "Image URL"), "InheritedSecurity" => GhostField.new("InheritedSecurity", "Bool", false, true, "Has Inherited Security?"), @@ -428,9 +462,10 @@ def internal_attribute_types "MajorWithMinorVersionsLimit" => GhostField.new("MajorWithMinorVersionsLimit", "Integer", false, true, "Major With Minor Versions Limit"), "MobileDefaultViewUrl" => GhostField.new("MobileDefaultViewUrl", "Text", false, true, "Mobile Default View URL"), "Modified" => GhostField.new("Modified", "StandardDateTime", false, true), - "MultipleDataList" => GhostField.new("MultipleDataList", "Bool", false, true, "Is Multiple Data List?"), + "MultipleDataList" => GhostField.new("MultipleDataList", "Bool", false, false, "Is Multiple Data List?"), "Name" => GhostField.new("Name", "Text", false, true), - "Ordered" => GhostField.new("Ordered", "Bool", false, true), + "OnQuickLaunch" => GhostField.new("OnQuickLaunch", "Bool", false, false), + "Ordered" => GhostField.new("Ordered", "Bool", false, false), "Permissions" => GhostField.new("Permissions", "Text", false, true), "ReadSecurity" => GhostField.new("ReadSecurity", "Integer", false, true, "Read Security"), "RequireCheckout" => GhostField.new("RequireCheckout", "Bool", false, true, "Requires Checkout?"), @@ -438,9 +473,9 @@ def internal_attribute_types "ScopeId" => GhostField.new("ScopeId", "Text", false, true, "Scope ID"), "SendToLocation" => GhostField.new("SendToLocation", "Text", false, true, "Send To Location"), "ServerTemplate" => GhostField.new("ServerTemplate", "Text", false, true, "Server Template"), - "ShowUser" => GhostField.new("ShowUser", "Bool", false, true, "Shows User?"), + "ShowUser" => GhostField.new("ShowUser", "Bool", false, false, "Shows User?"), "ThumbnailSize" => GhostField.new("ThumbnailSize", "Integer", false, true, "Thumbnail Size"), - "Title" => GhostField.new("Title", "Text", false, true), + "Title" => GhostField.new("Title", "Text", false, false), "ValidSecurityInfo" => GhostField.new("ValidSecurityInfo", "Bool", false, true, "Has Valid Security Info?"), "Version" => GhostField.new("Version", "Integer", false, true), "WebFullUrl" => GhostField.new("WebFullUrl", "Text", false, true, "Full Web URL"), @@ -547,6 +582,12 @@ def create_list_item(parameters) end end + def update_attributes_internal(attributes) + properties = Builder::XmlMarkup.new.List(attributes) + call("Lists", "UpdateList", "listName" => self.id, "listProperties" => properties) + reload + end + end end diff --git a/lib/activesp/list_template.rb b/lib/activesp/list_template.rb new file mode 100644 index 0000000..a947d51 --- /dev/null +++ b/lib/activesp/list_template.rb @@ -0,0 +1,84 @@ +# Copyright (c) 2010 XAOP bvba +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +module ActiveSP + + class ListTemplate < Base + + extend Caching + extend PersistentCaching + include Util + + attr_reader :Type + + persistent { |connection, type, *a| [connection, [:template, type]] } + # @private + def initialize(connection, type, attributes_before_type_cast) + @connection, @Type, @attributes_before_type_cast = connection, type, attributes_before_type_cast + end + + def key + encode_key("ML", [@Type]) + end + + # @private + def to_s + "#" + end + + # @private + alias inspect to_s + + private + + def original_attributes + @original_attributes ||= type_cast_attributes(@site, nil, internal_attribute_types, @attributes_before_type_cast) + end + + def internal_attribute_types + @@internal_attribute_types ||= { + "BaseType" => GhostField.new("BaseType", "Text", false, true), + "Description" => GhostField.new("Description", "Text", false, true), + "DisplayName" => GhostField.new("DisplayName", "Text", false, true), + "DocumentTemplate" => GhostField.new("DocumentTemplate", "Integer", false, true), + "DontSaveInTemplate" => GhostField.new("DontSaveInTemplate", "Bool", false, true), + "FeatureId" => GhostField.new("FeatureId", "Text", false, true), + "HiddenList" => GhostField.new("HiddenList", "Bool", false, true), + "Image" => GhostField.new("Image", "Text", false, true), + "FolderCreation" => GhostField.new("FolderCreation", "Bool", false, true), + "Hidden" => GhostField.new("Hidden", "Bool", false, true), + "OnQuickLaunch" => GhostField.new("OnQuickLaunch", "Bool", false, true), + "Name" => GhostField.new("Name", "Text", false, true), + "Sequence" => GhostField.new("Sequence", "Integer", false, true), + "SecurityBits" => GhostField.new("SecurityBits", "Text", false, true), + "Type" => GhostField.new("Type", "Integer", false, true), + "UseRootFolderForNavigation" => GhostField.new("UseRootFolderForNavigation", "Bool", false, true), + "VersioningEnabled" => GhostField.new("VersioningEnabled", "Bool", false, true) + } + end + + end + +end diff --git a/lib/activesp/root.rb b/lib/activesp/root.rb index c1ab3f9..55d519d 100644 --- a/lib/activesp/root.rb +++ b/lib/activesp/root.rb @@ -28,8 +28,10 @@ module ActiveSP # @private NS = { "sp" => "http://schemas.microsoft.com/sharepoint/soap/", + "SP" => "http://schemas.microsoft.com/sharepoint/", "z" => "#RowsetSchema", - "spdir" => "http://schemas.microsoft.com/sharepoint/soap/directory/" + "spdir" => "http://schemas.microsoft.com/sharepoint/soap/directory/", + "meet" => "http://schemas.microsoft.com/sharepoint/soap/meetings/" } module Root @@ -90,16 +92,28 @@ def roles end cache :roles, :dup => :always - def templates + def site_templates root.send(:call, "Sites", "GetSiteTemplates", "LCID" => 1033).xpath("//sp:Template", NS).map do |row| attributes = clean_attributes(row.attributes) - ActiveSP::Template.new(root, attributes["Name"], attributes) + ActiveSP::SiteTemplate.new(root, attributes["Name"], attributes) end end - cache :templates, :dup => :always + cache :site_templates, :dup => :always - def template(name) - templates.find { |template| template.Name == name } + def site_template(name) + site_templates.find { |template| template.Name == name } + end + + def list_templates + result = root.send(:call, "Webs", "GetListTemplates") + result.xpath("//SP:ListTemplate", NS).map do |row| + attributes = clean_attributes(row.attributes) + ActiveSP::ListTemplate.new(root, attributes["Type"].to_i, attributes) + end + end + + def list_template(type) + list_templates.find { |template| template.Type == type } end private diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index f9d5215..1e654ca 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -33,7 +33,7 @@ class Site < Base # The URL of this site # @return [String] - attr_reader :url + attr_reader :url # TODO: deprecate this in favor of Url # @private attr_reader :connection @@ -44,6 +44,14 @@ def initialize(connection, url, depth = 0) @services = {} end + def Url + @url + end + + def Name + ::File.basename(@url) + end + # @private def relative_url(url = @url) url[@connection.root_url.rindex("/") + 1..-1] @@ -77,13 +85,14 @@ def key # This documentation is not ideal. The ideal doesn't work out of the box encode_key("S", [@url[@connection.root_url.sub(/\/\z/, "").length + 1..-1], @depth]) end - # Returns the list of sites below this site. Does not recurse - # @return [Array] - def sites - result = call("Webs", "GetWebCollection") - result.xpath("//sp:Web", NS).map { |web| Site.new(connection, web["Url"].to_s, @depth + 1) } + def each_site(&blk) + __sites.each(&blk) + end + association :sites do + def create(attributes) + @object.create_site(attributes) + end end - cache :sites, :dup => :always # Returns the site with the given name. This name is what appears in the URL as name and is immutable. Return nil # if such a site does not exist @@ -96,24 +105,27 @@ def site(name) nil end - # Returns the list if lists in this sute. Does not recurse - # @return [Array] - def lists - result1 = call("Lists", "GetListCollection") - result2 = call("SiteData", "GetListCollection") - result2_by_id = {} - result2.xpath("//sp:_sList", NS).each do |element| - data = {} - element.children.each do |ch| - data[ch.name] = ch.inner_text - end - result2_by_id[data["InternalName"]] = data - end - result1.xpath("//sp:List", NS).select { |list| list["Title"] != "User Information List" }.map do |list| - List.new(self, list["ID"].to_s, list["Title"].to_s, clean_attributes(list.attributes), result2_by_id[list["ID"].to_s]) + def create_site(attributes) + template = attributes.delete("Template") + ActiveSP::SiteTemplate === template or raise ArgumentError, "wrong type for Template attribute" + title = attributes.delete("Title") + title or raise ArgumentError, "wrong type for Title attribute" + title = title.to_s + lcid = attributes.delete("Language") + Integer === lcid or raise ArgumentError, "wrong type for Language attribute" + parameters = type_check_attributes_for_creation(fields_by_name, attributes, false) + result = call("Meetings", "CreateWorkspace", "title" => title, "templateName" => template.Name, "lcid" => lcid) + Site.new(connection, result.xpath("//meet:CreateWorkspace", NS).first["Url"].to_s, @depth + 1) + end + + def each_list(&blk) + __lists.each(&blk) + end + association :lists do + def create(attributes) + @object.create_list(attributes) end end - cache :lists, :dup => :always # Returns the list with the given name. The name is what appears in the URL as name and is immutable. Returns nil # if such a list does not exist @@ -123,6 +135,19 @@ def list(name) lists.find { |list| ::File.basename(list.url) == name } end + def create_list(attributes) + template = attributes.delete("ServerTemplate") + ActiveSP::ListTemplate === template or raise ArgumentError, "wrong type for ServerTemplate attribute" + title = attributes.delete("Title") + title or raise ArgumentError, "wrong type for Title attribute" + title = title.to_s + description = attributes.delete("Description").to_s + parameters = type_check_attributes_for_creation(fields_by_name, attributes, false) + result = call("Lists", "AddList", "listName" => title, "description" => description, "templateID" => template.Type) + list = result.xpath("//sp:List", NS).first + List.new(self, list["ID"].to_s, list["Title"].to_s, clean_attributes(list.attributes)) + end + # Returns the site or list with the given name, or nil if it does not exist # @param [String] name The name of the site or list # @return [Site, List] @@ -185,10 +210,18 @@ def field(id) fields.find { |f| f.ID == id } end + def update_attributes(attributes) + attributes.each do |k, v| + set_attribute(k, v) + end + save + end + # See {Base#save} # @return [void] def save - p untype_cast_attributes(self, nil, internal_attribute_types, changed_attributes, false) + update_attributes_internal(untype_cast_attributes(self, nil, internal_attribute_types, changed_attributes, false)) + self end def accessible? @@ -198,6 +231,12 @@ def accessible? false end + def destroy + call("Dws", "DeleteDws") + supersite.__unregister_site(self) + self + end + # @private def to_s "#" @@ -206,6 +245,12 @@ def to_s # @private alias inspect to_s + #private + def __unregister_site(site) + p [:__unregister_site, self] + @__sites.delete(site) if @__sites + end + private def call(service, m, *args, &blk) @@ -236,10 +281,10 @@ def attributes_before_type_cast element.children.each do |ch| result[ch.name] = ch.inner_text end - result + result.merge("Url" => @url, "Name" => self.Name) else element = data.xpath("//sp:Web", NS).first - clean_attributes(element.attributes) + clean_attributes(element.attributes).merge("Url" => @url, "Name" => self.Name) end end cache :attributes_before_type_cast @@ -261,8 +306,10 @@ def internal_attribute_types "Language" => GhostField.new("Language", "Integer", false, true), "LastModified" => GhostField.new("LastModified", "XMLDateTime", false, true, "Modified"), "LastModifiedForceRecrawl" => GhostField.new("LastModifiedForceRecrawl", "XMLDateTime", false, true, "Last Modified Force Recrawl"), + "Name" => GhostField.new("Name", "Text", false, true), "Permissions" => GhostField.new("Permissions", "Text", false, true), - "Title" => GhostField.new("Title", "Text", false, true), + "Title" => GhostField.new("Title", "Text", false, false), + "Url" => GhostField.new("Url", "Text", false, true), "UsedInAutocat" => GhostField.new("UsedInAutocat", "Bool", false, true, "Used in Autocat?"), "ValidSecurityInfo" => GhostField.new("ValidSecurityInfo", "Bool", false, true, "Has Valid Security Info?"), "WebID" => GhostField.new("WebID", "Text", false, true, "Web ID") @@ -278,6 +325,35 @@ def permissions end cache :permissions, :dup => :always + def update_attributes_internal(attributes) + call("Dws", "RenameDws", "title" => attributes["Title"]) + reload + end + + def __sites + p [:__sites, self] + result = call("Webs", "GetWebCollection") + result.xpath("//sp:Web", NS).map { |web| Site.new(connection, web["Url"].to_s, @depth + 1) } + end + cache :__sites, :dup => :always + + def __lists + result1 = call("Lists", "GetListCollection") + result2 = call("SiteData", "GetListCollection") + result2_by_id = {} + result2.xpath("//sp:_sList", NS).each do |element| + data = {} + element.children.each do |ch| + data[ch.name] = ch.inner_text + end + result2_by_id[data["InternalName"]] = data + end + result1.xpath("//sp:List", NS).select { |list| list["Title"] != "User Information List" }.map do |list| + List.new(self, list["ID"].to_s, list["Title"].to_s, clean_attributes(list.attributes), result2_by_id[list["ID"].to_s]) + end + end + cache :__lists, :dup => :always + # @private class Service diff --git a/lib/activesp/template.rb b/lib/activesp/site_template.rb similarity index 97% rename from lib/activesp/template.rb rename to lib/activesp/site_template.rb index 863fc4b..de2f533 100644 --- a/lib/activesp/template.rb +++ b/lib/activesp/site_template.rb @@ -25,7 +25,7 @@ module ActiveSP - class Template < Base + class SiteTemplate < Base extend Caching extend PersistentCaching @@ -39,6 +39,10 @@ def initialize(connection, name, attributes_before_type_cast) @connection, @Name, @attributes_before_type_cast = connection, name, attributes_before_type_cast end + def key + encode_key("MS", [@Name]) + end + # @private def to_s "#" diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 988ee77..20c8ef7 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -226,11 +226,14 @@ def type_check_attribute(field, value, override_restrictions) raise ArgumentError, "wrong value for #{field.Name} attribute" end when "Computed" - if override_restrictions + # ContentType is Computed in SP 2011 + if override_restrictions || field.Name == "ContentType" value.to_s else raise "not yet #{field.Name}:#{field.internal_type}" end + when "ListReference" + ActiveSP::List === value and value or raise ArgumentError, "wrong type for #{field.Name} attribute" else raise "not yet #{field.Name}:#{field.internal_type}" end @@ -277,11 +280,13 @@ def untype_cast_attributes(site, list, fields, attributes, override_restrictions when "ContentTypeId" when "ThreadIndex" when "Computed" - if override_restrictions + if override_restrictions || k == "ContentType" v = v.to_s else raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" end + when "ListReference" + v = v.ID else raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" end From 32f1dd154fb2a75fb3bac238f286cfd55bff131f Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Mon, 25 Jul 2011 15:28:10 +0200 Subject: [PATCH 10/32] Support for empty Lookup fields --- lib/activesp/util.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 20c8ef7..851a495 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -169,6 +169,8 @@ def type_check_attribute(field, value, override_restrictions) if field.List if ::ActiveSP::Item === value && value.list == field.List value + elsif nil == value + nil else raise ArgumentError, "wrong type for #{field.Name} attribute" end From 08346d50ecfad879a228e4d357942e156c25c7b0 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Wed, 27 Jul 2011 16:58:27 +0200 Subject: [PATCH 11/32] Allow specifying an absolute URL when fetching content an doing head requests --- lib/activesp/connection.rb | 42 ++++---------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 747a808..8a4833d 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -179,25 +179,8 @@ def fetch(url) u = URL(@root_url) [u.host, u.port] end - # Net::HTTP.start(*@open_params) do |http| - # request = Net::HTTP::Get.new(URL(url).full_path.gsub(/ /, "%20")) - # if @login - # case auth_type - # when :ntlm - # request.ntlm_auth(@login, @password) - # when :basic - # request.basic_auth(@login, @password) - # else - # raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" - # end - # end - # response = http.request(request) - # # if Net::HTTPFound === response - # # response = fetch(response["location"]) - # # end - # # response - # end - request = HTTPI::Request.new("http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}") + url = "http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url + request = HTTPI::Request.new(url) if login case auth_type when :ntlm @@ -221,25 +204,8 @@ def head(url) u = URL(@root_url) [u.host, u.port] end - # Net::HTTP.start(*@open_params) do |http| - # request = Net::HTTP::Head.new(URL(url).full_path.gsub(/ /, "%20")) - # if @login - # case auth_type - # when :ntlm - # request.ntlm_auth(@login, @password) - # when :basic - # request.basic_auth(@login, @password) - # else - # raise ArgumentError, "Unknown authentication type #{auth_type.inspect}" - # end - # end - # response = http.request(request) - # # if Net::HTTPFound === response - # # response = fetch(response["location"]) - # # end - # # response - # end - request = HTTPI::Request.new("http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}") + url = "http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url + request = HTTPI::Request.new(url) if login case auth_type when :ntlm From 1e3249cc1da18aa2f142a4fb22914fca11bd607e Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Wed, 21 Sep 2011 13:24:36 +0200 Subject: [PATCH 12/32] Remove debug output --- lib/activesp/site.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index 1e654ca..73e7e7a 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -331,7 +331,6 @@ def update_attributes_internal(attributes) end def __sites - p [:__sites, self] result = call("Webs", "GetWebCollection") result.xpath("//sp:Web", NS).map { |web| Site.new(connection, web["Url"].to_s, @depth + 1) } end From 2943596ea8e9f628d408982908f478b6e4154ec9 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Thu, 22 Sep 2011 13:50:56 +0200 Subject: [PATCH 13/32] A few optimizations --- lib/activesp.rb | 1 + lib/activesp/connection.rb | 3 ++- lib/activesp/list.rb | 26 ++++++++++++++++--- lib/activesp/root.rb | 2 +- lib/activesp/site.rb | 7 +++++ lib/activesp/user_group_proxy.rb | 44 ++++++++++++++++++++++++++++++++ lib/activesp/util.rb | 29 ++++++++++++++++++++- 7 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 lib/activesp/user_group_proxy.rb diff --git a/lib/activesp.rb b/lib/activesp.rb index 71d7b04..e768358 100644 --- a/lib/activesp.rb +++ b/lib/activesp.rb @@ -50,6 +50,7 @@ module ActiveSP require 'activesp/ghost_field' require 'activesp/user' require 'activesp/group' +require 'activesp/user_group_proxy' require 'activesp/role' require 'activesp/permission_set' require 'activesp/file' diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 8a4833d..79e0831 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -101,7 +101,7 @@ class Connection # @private # TODO: create profile - attr_reader :login, :password, :auth_type, :root_url, :trace + attr_reader :login, :password, :auth_type, :root_url, :trace, :user_group_proxy # @param [Hash] options The connection options # @option options [String] :root The URL of the root site @@ -115,6 +115,7 @@ def initialize(options = {}) @password = options.delete(:password) @auth_type = options.delete(:auth_type) || :ntlm @trace = options.delete(:trace) + @user_group_proxy = options.delete(:user_group_proxy) options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}" cache = nil configure_persistent_cache { |c| cache ||= c } diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 8d64496..2fca9f7 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -46,14 +46,29 @@ def initialize(site, id, title = nil, attributes_before_type_cast1 = nil, attrib @Title = title if title # This testing for emptiness of RootFolder is necessary because it is empty # in bulk calls. - @attributes_before_type_cast1 = attributes_before_type_cast1 if attributes_before_type_cast1 && attributes_before_type_cast1["RootFolder"] != "" - @attributes_before_type_cast2 = attributes_before_type_cast2 if attributes_before_type_cast2 && attributes_before_type_cast2["RootFolder"] != "" + @attributes_before_type_cast1 = attributes_before_type_cast1 if attributes_before_type_cast1 + @attributes_before_type_cast2 = attributes_before_type_cast2 if attributes_before_type_cast2 + end + + def RootFolder + if attributes_before_type_cast1["RootFolder"] == "" + clear_cache_for("attributes_before_type_cast1") + end + attributes_before_type_cast1["RootFolder"] + end + + %w[AllowAnonymousAccess AnonymousViewListItems BaseTemplate InheritedSecurity LastModified LastModifiedForceRecrawl ValidSecurityInfo Author].each do |attribute| + eval <<-RUBY + def #{attribute} + + end + RUBY end # The URL of the list # @return [String] def url - URL(@site.url).join(attributes["RootFolder"]).to_s + URL(@site.url).join(self.RootFolder).to_s # # Dirty. Used to use RootFolder, but if you get the data from the bulk calls, RootFolder is the empty # # string rather than what it should be. That's what you get with web services as an afterthought I guess. # view_url = ::File.dirname(attributes["DefaultViewUrl"]) @@ -388,6 +403,10 @@ def ==(object) ::ActiveSP::List === object && self.ID == object.ID end + def quick_attributes + type_cast_attributes(@site, nil, internal_attribute_types, attributes_before_type_cast1) + end + private def data1 @@ -416,6 +435,7 @@ def attributes_before_type_cast2 cache :attributes_before_type_cast2 def original_attributes + self.RootFolder attrs = attributes_before_type_cast1.merge(attributes_before_type_cast2).merge("BaseType" => attributes_before_type_cast1["BaseType"]) type_cast_attributes(@site, nil, internal_attribute_types, attrs) end diff --git a/lib/activesp/root.rb b/lib/activesp/root.rb index 55d519d..1e0ed99 100644 --- a/lib/activesp/root.rb +++ b/lib/activesp/root.rb @@ -48,7 +48,7 @@ def root # Returns the list of users in the system # @return [Array] def users - root.send(:call, "UserGroup", "get_user_collection_from_site").xpath("//spdir:User", NS).map do |row| + root.send(:call, "UserGroup", "GetUserCollectionFromSite").xpath("//spdir:User", NS).map do |row| attributes = clean_attributes(row.attributes) User.new(root, attributes["LoginName"]) end diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index 73e7e7a..4c90250 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -251,6 +251,13 @@ def __unregister_site(site) @__sites.delete(site) if @__sites end + def quick_attributes + { + "Name" => self.Name, + "Url" => self.Url + } + end + private def call(service, m, *args, &blk) diff --git a/lib/activesp/user_group_proxy.rb b/lib/activesp/user_group_proxy.rb new file mode 100644 index 0000000..381b131 --- /dev/null +++ b/lib/activesp/user_group_proxy.rb @@ -0,0 +1,44 @@ +# Copyright (c) 2010 XAOP bvba +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +module ActiveSP + + class UserGroupProxy + + def initialize(blk) + @blk = blk + end + + def __user_group + @__user_group ||= @blk.call + end + + def method_missing(m, *a, &b) + __user_group.send(m, *a, &b) + end + + end + +end diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 851a495..9eab63a 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -122,7 +122,14 @@ def type_cast_attributes(site, list, fields, attributes) result end + # TODO: check if this is still needed def create_user_or_group(site, entry) + with_user_proxy(site) do + create_user_or_group_no_proxy(site, entry) + end + end + + def create_user_or_group_no_proxy(site, entry) if entry[/\\/] User.new(site.connection.root, entry) else @@ -131,8 +138,14 @@ def create_user_or_group(site, entry) end def create_user_or_group_by_name(site, name) + with_user_proxy(site) do + create_user_or_group_by_name_no_proxy(site, name) + end + end + + def create_user_or_group_by_name_no_proxy(site, name) if /\A\d+\z/ === name - create_user_or_group_by_id(site, name) + create_user_or_group_by_id_no_proxy(site, name) else if user = site.connection.users.find { |u| u.attribute("Name") === name } User.new(site.connection.root, user.attribute("LoginName")) @@ -143,6 +156,12 @@ def create_user_or_group_by_name(site, name) end def create_user_or_group_by_id(site, id) + with_user_proxy(site) do + create_user_or_group_by_id_no_proxy(site, id) + end + end + + def create_user_or_group_by_id_no_proxy(site, id) if user = site.connection.users.find { |u| u.attribute("ID") === id } User.new(site.connection.root, user.attribute("LoginName")) elsif group = site.connection.groups.find { |g| g.attribute("ID") === id } @@ -150,6 +169,14 @@ def create_user_or_group_by_id(site, id) end end + def with_user_proxy(site, &blk) + if site.connection.user_group_proxy + ::ActiveSP::UserGroupProxy.new(blk) + else + blk.call + end + end + def type_check_attribute(field, value, override_restrictions) case field.internal_type when "Text", "File", "Note", "URL", "Choice" From 99134f3142aa0e8e33f707937f8f30c1856e7cb3 Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Thu, 22 Sep 2011 16:41:17 +0200 Subject: [PATCH 14/32] Add relative_url --- lib/activesp/base.rb | 10 ++++++++++ lib/activesp/list.rb | 25 +++++++------------------ lib/activesp/site.rb | 5 ----- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/activesp/base.rb b/lib/activesp/base.rb index c02ddd6..1354a00 100644 --- a/lib/activesp/base.rb +++ b/lib/activesp/base.rb @@ -31,6 +31,16 @@ class Base extend Caching extend Associations + # @private + def relative_url(site_or_list = @list.site.connection.root) + reference_url = site_or_list.url + reference_url += "/" unless reference_url[-1, 1] == "/" + url = self.url + reference_url = reference_url.sub(/\Ahttps?:\/\/[^\/]+/, "") + url = url.sub(/\Ahttps?:\/\/[^\/]+/, "") + url[reference_url.length..-1] + end + # Returns a key that can be used to retrieve this object later on using {Connection#find_by_key} # @return [String] def key diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 2fca9f7..19be35d 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -57,32 +57,21 @@ def RootFolder attributes_before_type_cast1["RootFolder"] end - %w[AllowAnonymousAccess AnonymousViewListItems BaseTemplate InheritedSecurity LastModified LastModifiedForceRecrawl ValidSecurityInfo Author].each do |attribute| - eval <<-RUBY - def #{attribute} - - end - RUBY - end - # The URL of the list # @return [String] def url URL(@site.url).join(self.RootFolder).to_s - # # Dirty. Used to use RootFolder, but if you get the data from the bulk calls, RootFolder is the empty - # # string rather than what it should be. That's what you get with web services as an afterthought I guess. - # view_url = ::File.dirname(attributes["DefaultViewUrl"]) - # result = URL(@site.url).join(view_url).to_s - # if ::File.basename(result) == "Forms" and dir = ::File.dirname(result) and dir.length > @site.url.length - # result = dir - # end - # result end cache :url # @private - def relative_url - @site.relative_url(url) + def relative_url(site = @site.connection.root) + reference_url = site.url + reference_url += "/" unless reference_url[-1, 1] == "/" + url = self.url + reference_url = reference_url.sub(/\Ahttps?:\/\/[^\/]+/, "") + url = url.sub(/\Ahttps?:\/\/[^\/]+/, "") + url[reference_url.length..-1] end # See {Base#key} diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index 4c90250..e8475ef 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -52,11 +52,6 @@ def Name ::File.basename(@url) end - # @private - def relative_url(url = @url) - url[@connection.root_url.rindex("/") + 1..-1] - end - # Returns the containing site, or nil if this is the root site # @return [Site] def supersite From 4c7a11d70b879ef20b398c373fe2f62dbcb2445c Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 23 Sep 2011 11:37:39 +0200 Subject: [PATCH 15/32] Fix to relative_url --- lib/activesp/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activesp/base.rb b/lib/activesp/base.rb index 1354a00..e90a3f6 100644 --- a/lib/activesp/base.rb +++ b/lib/activesp/base.rb @@ -32,7 +32,7 @@ class Base extend Associations # @private - def relative_url(site_or_list = @list.site.connection.root) + def relative_url(site_or_list = @list ? @list.site.connection.root : connection.root) reference_url = site_or_list.url reference_url += "/" unless reference_url[-1, 1] == "/" url = self.url From 4c5b5bd640c51e4bf4bf1982edd3e3282c3d449a Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Fri, 23 Sep 2011 16:01:25 +0200 Subject: [PATCH 16/32] Fix when a subfolder is created and it already exists --- lib/activesp/folder.rb | 4 ++-- lib/activesp/list.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/activesp/folder.rb b/lib/activesp/folder.rb index 92c9b4a..f942946 100644 --- a/lib/activesp/folder.rb +++ b/lib/activesp/folder.rb @@ -58,11 +58,11 @@ def create!(parameters = {}) end def create_folder(parameters = {}) - @list.create_folder(parameters.merge(:folder => absolute_url)) + @list.create_folder(parameters.merge(:folder => absolute_url, :folder_object => self)) end def create_folder!(parameters = {}) - @list.create_folder!(parameters.merge(:folder => absolute_url)) + @list.create_folder!(parameters.merge(:folder => absolute_url, :folder_object => self)) end def each_document(options = {}, &blk) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 19be35d..000a08f 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -582,7 +582,7 @@ def create_list_item(parameters) error_code = create_result.xpath("./sp:ErrorCode", NS).first error_code &&= error_code.text.to_s if error_code == "0x8107090d" - raise ActiveSP::AlreadyExists.new(error_text.text.to_s) { item(folder_name) } + raise ActiveSP::AlreadyExists.new(error_text.text.to_s) { (folder_object || self).item(folder_name) } else # Make it look like the error came from soap # Alternatively we could wrap all the soap faults maybe From f71ce552b47605b449da641857474f5b4999f2ea Mon Sep 17 00:00:00 2001 From: Peter Vanbroekhoven Date: Mon, 26 Sep 2011 09:53:08 +0200 Subject: [PATCH 17/32] Escape urls for attachments --- lib/activesp/item.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/activesp/item.rb b/lib/activesp/item.rb index 919997a..e969e21 100644 --- a/lib/activesp/item.rb +++ b/lib/activesp/item.rb @@ -114,7 +114,7 @@ def key def attachment_urls @list.when_list do result = call("Lists", "GetAttachmentCollection", "listName" => @list.id, "listItemID" => @id) - return result.xpath("//sp:Attachment", NS).map { |att| att.text } + return result.xpath("//sp:Attachment", NS).map { |att| att.text.gsub(/ /, "%20") } end @list.when_document_library { raise TypeError, "a document library does not support attachments" } @list.raise_on_unknown_type @@ -122,7 +122,6 @@ def attachment_urls cache :attachment_urls, :dup => :always # Yields each attachment as a ActiveSP::File object. - # def each_attachment attachment_urls.each { |url| yield ActiveSP::File.new(self, url, true) } end From 3ba997d99d4c05cbd85eab7e87840c52d851ae49 Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Tue, 10 Jan 2012 15:25:19 +0100 Subject: [PATCH 18/32] Allow higher versions of Savon than exactly 0.9.2 Since monkeypatches are present ActiveSP this is somewhat risky, but at least version 0.9.7 should work --- activesp.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesp.gemspec b/activesp.gemspec index bad54f2..0563bdc 100644 --- a/activesp.gemspec +++ b/activesp.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.files += Dir['lib/**/*.rb'] # s.bindir = "bin" # s.executables.push(*(Dir['bin/*.rb'])) - s.add_dependency('savon', '= 0.9.2') + s.add_dependency('savon', '~> 0.9.2') s.add_dependency('curb') s.add_dependency('httpi', '= 0.9.4') # s.rdoc_options << '--exclude' << 'ext' << '--main' << 'README' From 7352f478ce62f0dd229d9376d62d028c97feebca Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Tue, 10 Jan 2012 15:56:06 +0100 Subject: [PATCH 19/32] Apparently we need 1.0 1.0 Should work and has the patch for WSDL fetching failures --- activesp.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesp.gemspec b/activesp.gemspec index 0563bdc..7992d26 100644 --- a/activesp.gemspec +++ b/activesp.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.files += Dir['lib/**/*.rb'] # s.bindir = "bin" # s.executables.push(*(Dir['bin/*.rb'])) - s.add_dependency('savon', '~> 0.9.2') + s.add_dependency('savon', '= 1.0.0') s.add_dependency('curb') s.add_dependency('httpi', '= 0.9.4') # s.rdoc_options << '--exclude' << 'ext' << '--main' << 'README' From b633bb19bc402ee343de8d259545d11688ed7790 Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Wed, 15 Feb 2012 14:38:48 +0100 Subject: [PATCH 20/32] Version 0.9.8 contains the necessary patch --- activesp.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesp.gemspec b/activesp.gemspec index 7992d26..616090b 100644 --- a/activesp.gemspec +++ b/activesp.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.files += Dir['lib/**/*.rb'] # s.bindir = "bin" # s.executables.push(*(Dir['bin/*.rb'])) - s.add_dependency('savon', '= 1.0.0') + s.add_dependency('savon', '>= 0.9.8') s.add_dependency('curb') s.add_dependency('httpi', '= 0.9.4') # s.rdoc_options << '--exclude' << 'ext' << '--main' << 'README' From 6acb4d00399e9cc036988f11b4c51759d433b723 Mon Sep 17 00:00:00 2001 From: France Julien Date: Tue, 6 Mar 2012 10:58:26 +0100 Subject: [PATCH 21/32] Add row_limit option to GetListItems and GetListChangesSinceToken queries. --- lib/activesp/list.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 000a08f..5861945 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -203,6 +203,7 @@ def create_folder!(parameters = {}) def changes_since_token(token, options = {}) options = options.dup no_preload = options.delete(:no_preload) + row_limit = options.delete(:row_limit).try(:to_s) options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}" if no_preload @@ -213,9 +214,9 @@ def changes_since_token(token, options = {}) view_fields = Builder::XmlMarkup.new.ViewFields end if token - result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields) + result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields, 'rowLimit' => row_limit) else - result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields) + result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields, 'rowLimit' => row_limit) end updates = [] result.xpath("//z:row", NS).each do |row| @@ -506,8 +507,10 @@ def permissions end cache :permissions, :dup => :always - def get_list_items(view_fields, query_options, query) - result = call("Lists", "GetListItems", { "listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options }.merge(query)) + def get_list_items(view_fields, query_options, query, options = {}) + options = options.dup + row_limit = options.delete(:row_limit).try(:to_s) + result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options, "rowLimit" => row_limit}.merge(query)) result.xpath("//z:row", NS).each do |row| yield clean_item_attributes(row.attributes) end From e9e26019a90ad6277a9409e1373d5a6350330957 Mon Sep 17 00:00:00 2001 From: France Julien Date: Tue, 6 Mar 2012 11:19:33 +0100 Subject: [PATCH 22/32] Don't use 'try' method which come from rails. Sorry. --- lib/activesp/list.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 5861945..6d55454 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -203,7 +203,7 @@ def create_folder!(parameters = {}) def changes_since_token(token, options = {}) options = options.dup no_preload = options.delete(:no_preload) - row_limit = options.delete(:row_limit).try(:to_s) + row_limit = (r_l = options.delete(:row_limit)) ? r_l.to_s : nil options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}" if no_preload @@ -509,7 +509,7 @@ def permissions def get_list_items(view_fields, query_options, query, options = {}) options = options.dup - row_limit = options.delete(:row_limit).try(:to_s) + row_limit = (r_l = options.delete(:row_limit)) ? r_l.to_s : nil result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options, "rowLimit" => row_limit}.merge(query)) result.xpath("//z:row", NS).each do |row| yield clean_item_attributes(row.attributes) From 8b59ded17daa957a5c96b92008bc52ac571da402 Mon Sep 17 00:00:00 2001 From: France Julien Date: Tue, 6 Mar 2012 14:00:33 +0100 Subject: [PATCH 23/32] :row_limit => nil give errors when the token is present. (Apparenty not when no token...) --- lib/activesp/list.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 6d55454..7596e70 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -203,7 +203,7 @@ def create_folder!(parameters = {}) def changes_since_token(token, options = {}) options = options.dup no_preload = options.delete(:no_preload) - row_limit = (r_l = options.delete(:row_limit)) ? r_l.to_s : nil + row_limit = (r_l = options.delete(:row_limit)) ? {'rowLimit' => r_l.to_s} : {} options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}" if no_preload @@ -214,9 +214,9 @@ def changes_since_token(token, options = {}) view_fields = Builder::XmlMarkup.new.ViewFields end if token - result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields, 'rowLimit' => row_limit) + result = call("Lists", "GetListItemChangesSinceToken", {"listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields}.merge(ro w_limit)) else - result = call("Lists", "GetListItemChangesSinceToken", "listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields, 'rowLimit' => row_limit) + result = call("Lists", "GetListItemChangesSinceToken", {"listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields}.merge(row_limit)) end updates = [] result.xpath("//z:row", NS).each do |row| @@ -509,8 +509,8 @@ def permissions def get_list_items(view_fields, query_options, query, options = {}) options = options.dup - row_limit = (r_l = options.delete(:row_limit)) ? r_l.to_s : nil - result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options, "rowLimit" => row_limit}.merge(query)) + row_limit = (r_l = options.delete(:row_limit)) ? {'rowLimit' => r_l.to_s} : {} + result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options, "rowLimit" => row_limit}.merge(query).merge(row_limit)) result.xpath("//z:row", NS).each do |row| yield clean_item_attributes(row.attributes) end From 144e35519a2ee04083b2f0caf68c87c64d554541 Mon Sep 17 00:00:00 2001 From: France Julien Date: Wed, 11 Apr 2012 09:44:31 +0200 Subject: [PATCH 24/32] row_limit instead of 'ro w_limit' (stange that I didn't catch this error before.) --- lib/activesp/list.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 7596e70..0d7ac7c 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -214,7 +214,7 @@ def changes_since_token(token, options = {}) view_fields = Builder::XmlMarkup.new.ViewFields end if token - result = call("Lists", "GetListItemChangesSinceToken", {"listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields}.merge(ro w_limit)) + result = call("Lists", "GetListItemChangesSinceToken", {"listName" => @id, 'queryOptions' => '', 'changeToken' => token, 'viewFields' => view_fields}.merge(row_limit)) else result = call("Lists", "GetListItemChangesSinceToken", {"listName" => @id, 'queryOptions' => '', 'viewFields' => view_fields}.merge(row_limit)) end From 1176ae4c8c63450589285b6a7712dd1e90ac80d1 Mon Sep 17 00:00:00 2001 From: France Julien Date: Thu, 15 Nov 2012 16:03:41 +0100 Subject: [PATCH 25/32] with wasabi authentication. We can now get the wsdl document when the site need authenticaiton. And we can then get the endpoint --- lib/activesp/connection.rb | 1 + lib/activesp/site.rb | 15 +++++++++------ lib/activesp/wasabi_authentication.rb | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 lib/activesp/wasabi_authentication.rb diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 79e0831..551379b 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -24,6 +24,7 @@ # OTHER DEALINGS IN THE SOFTWARE. require 'savon' +require 'activesp/wasabi_authentication' require 'net/ntlm_http' Savon.configure do |config| diff --git a/lib/activesp/site.rb b/lib/activesp/site.rb index e8475ef..60cc17d 100644 --- a/lib/activesp/site.rb +++ b/lib/activesp/site.rb @@ -363,20 +363,23 @@ def initialize(site, name) @client = Savon::Client.new do |wsdl, http| wsdl.document = ::File.join(URI.escape(site.url), "_vti_bin", name + ".asmx?WSDL") if site.connection.login - case site.connection.auth_type + auth_type = site.connection.auth_type + login = site.connection.login + password = site.connection.password + wsdl.authenticate(:method => auth_type, :usename => login, :password => password) + case auth_type when :ntlm - http.auth.ntlm(site.connection.login, site.connection.password) + http.auth.ntlm(login, password) when :basic - http.auth.basic(site.connection.login, site.connection.password) + http.auth.basic(login, password) when :digest - http.auth.digest(site.connection.login, site.connection.password) + http.auth.digest(login, password) when :gss_negotiate - http.auth.gssnegotiate(site.connection.login, site.connection.password) + http.auth.gssnegotiate(login, password) else raise ArgumentError, "Unknown authentication type #{site.connection.auth_type.inspect}" end end - end end diff --git a/lib/activesp/wasabi_authentication.rb b/lib/activesp/wasabi_authentication.rb new file mode 100644 index 0000000..ad9d5b2 --- /dev/null +++ b/lib/activesp/wasabi_authentication.rb @@ -0,0 +1,19 @@ +module Savon + module Wasabi + class Document + def authenticate(options) + @request ||= HTTPI::Request.new + case options[:method] + when :ntlm + @request.auth.ntlm(options[:usename], options[:password]) + when :basic + @request.auth.basic(options[:usename], options[:password]) + when :digest + @request.auth.digest(options[:usename], options[:password]) + when :gss_negotiate + @request.auth.gssnegotiate(options[:usename], options[:password]) + end + end + end + end +end From 2a431920862cebcfb4fe3d848480ffdd19c8d3dc Mon Sep 17 00:00:00 2001 From: France Julien Date: Mon, 19 Nov 2012 17:40:44 +0100 Subject: [PATCH 26/32] RowLimit Bux fixed. --- lib/activesp/list.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 0d7ac7c..ac805de 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -510,7 +510,7 @@ def permissions def get_list_items(view_fields, query_options, query, options = {}) options = options.dup row_limit = (r_l = options.delete(:row_limit)) ? {'rowLimit' => r_l.to_s} : {} - result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options, "rowLimit" => row_limit}.merge(query).merge(row_limit)) + result = call("Lists", "GetListItems", {"listName" => @id, "viewFields" => view_fields, "queryOptions" => query_options}.merge(query).merge(row_limit)) result.xpath("//z:row", NS).each do |row| yield clean_item_attributes(row.attributes) end From e579f416b1ae2e21d87d0a79adbbb818b5f7b725 Mon Sep 17 00:00:00 2001 From: France Julien Date: Tue, 20 Nov 2012 14:51:17 +0100 Subject: [PATCH 27/32] Will be much faster to retreive the attributes of type User. + some other fixes and improvements --- lib/activesp/list.rb | 9 ++++++++- lib/activesp/root.rb | 2 +- lib/activesp/util.rb | 20 ++++++++++---------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index ac805de..270438a 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -204,6 +204,7 @@ def changes_since_token(token, options = {}) options = options.dup no_preload = options.delete(:no_preload) row_limit = (r_l = options.delete(:row_limit)) ? {'rowLimit' => r_l.to_s} : {} + only_attrs = options.delete(:only_attrs) options.empty? or raise ArgumentError, "unknown options #{options.keys.map { |k| k.inspect }.join(", ")}" if no_preload @@ -221,7 +222,13 @@ def changes_since_token(token, options = {}) updates = [] result.xpath("//z:row", NS).each do |row| attributes = clean_item_attributes(row.attributes) - updates << construct_item(:unset, attributes, no_preload ? nil : attributes) + all_attrs = only_attrs ? only_attrs.inject({}) do |h, a| + if attributes.has_key?(a) + h[a] = attributes[a] + end + h + end : attributes + updates << construct_item(:unset, attributes, no_preload ? nil : all_attrs) end deletes = [] result.xpath("//sp:Changes/sp:Id", NS).each do |row| diff --git a/lib/activesp/root.rb b/lib/activesp/root.rb index 1e0ed99..edfc421 100644 --- a/lib/activesp/root.rb +++ b/lib/activesp/root.rb @@ -50,7 +50,7 @@ def root def users root.send(:call, "UserGroup", "GetUserCollectionFromSite").xpath("//spdir:User", NS).map do |row| attributes = clean_attributes(row.attributes) - User.new(root, attributes["LoginName"]) + User.new(root, attributes["LoginName"], attributes) end end cache :users, :dup => :always diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 9eab63a..1d13641 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -58,7 +58,7 @@ def type_cast_attributes(site, list, fields, attributes) else v = Time.xmlschema(v.sub(/ /, "T")) end - when "Computed", "Text", "Guid", "ContentTypeId", "URL" + when "Computed", "Text", "Guid", "ContentTypeId", "URL", "Calculated" when "Integer", "Counter", "Attachments" v = v && v != "" ? Integer(v) : nil when "ModStat" # 0 @@ -68,7 +68,7 @@ def type_cast_attributes(site, list, fields, attributes) v = v == "1" when "Bool" v = !!v[/true/i] - when "File" + when "File", "TaxonomyFieldType" v = v.sub(/\A\d+;#/, "") when "Note" @@ -79,11 +79,11 @@ def type_cast_attributes(site, list, fields, attributes) v = create_user_or_group_by_name(site, v) when "UserMulti" d = split_multi(v) - v = (0...(d.length / 4)).map { |i| create_user_or_group(site, d[4 * i + 2]) } + v = (0...(d.length / 4)).map { |i| create_user_or_group_by_name(site, d[4 * i + 2]) } when "Choice" # For some reason there is no encoding here - when "MultiChoice" + when "MultiChoice", "TaxonomyFieldTypeMulti" # SharePoint disallows ;# inside choices and starts with a ;# v = v.split(/;#/)[1..-1] @@ -104,7 +104,7 @@ def type_cast_attributes(site, list, fields, attributes) when "ThreadIndex" else - # raise NotImplementedError, "don't know type #{field.type.inspect} for #{k}=#{v.inspect}" + # raise NotImplementedError, "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect}" # Note: can't print self if it needs the attributes to be loaded, so just display the class # warn "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on #{self.class}" end @@ -148,9 +148,9 @@ def create_user_or_group_by_name_no_proxy(site, name) create_user_or_group_by_id_no_proxy(site, name) else if user = site.connection.users.find { |u| u.attribute("Name") === name } - User.new(site.connection.root, user.attribute("LoginName")) - elsif group = site.connection.groups.find { |g| g.attribute("Name") === name } - Group.new(site.connection.root, name) + user + elsif group = site.connection.group(name) + group end end end @@ -163,9 +163,9 @@ def create_user_or_group_by_id(site, id) def create_user_or_group_by_id_no_proxy(site, id) if user = site.connection.users.find { |u| u.attribute("ID") === id } - User.new(site.connection.root, user.attribute("LoginName")) + user elsif group = site.connection.groups.find { |g| g.attribute("ID") === id } - Group.new(site.connection.root, group.attribute("Name")) + group end end From 82171febfcc7ec09a1cf17fdf11748de69b6d53c Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Fri, 1 Feb 2013 18:05:09 +0100 Subject: [PATCH 28/32] Fix for TaxonomyFieldTypeMulti --- lib/activesp/util.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 1d13641..b96a07c 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -68,7 +68,7 @@ def type_cast_attributes(site, list, fields, attributes) v = v == "1" when "Bool" v = !!v[/true/i] - when "File", "TaxonomyFieldType" + when "File" v = v.sub(/\A\d+;#/, "") when "Note" @@ -83,7 +83,7 @@ def type_cast_attributes(site, list, fields, attributes) when "Choice" # For some reason there is no encoding here - when "MultiChoice", "TaxonomyFieldTypeMulti" + when "MultiChoice" # SharePoint disallows ;# inside choices and starts with a ;# v = v.split(/;#/)[1..-1] @@ -101,6 +101,14 @@ def type_cast_attributes(site, list, fields, attributes) else v = (0...(d.length / 4)).map { |i| d[4 * i + 2] } end + when "TaxonomyFieldType" + d = split_multi(v) + # TODO: lookup translated values in metadata store? + v = d[2] + when "TaxonomyFieldTypeMulti" + d = split_multi(v) + # TODO: lookup translated values in metadata store? + v = (0...(d.length / 4)).map { |i| d[4 * i + 2] } when "ThreadIndex" else From b95b5560f60a251ad6f812ac41b8b8cb0d7fc6f4 Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Tue, 2 Apr 2013 15:05:19 +0200 Subject: [PATCH 29/32] Add support for https --- lib/activesp/connection.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/activesp/connection.rb b/lib/activesp/connection.rb index 551379b..e48da61 100644 --- a/lib/activesp/connection.rb +++ b/lib/activesp/connection.rb @@ -176,12 +176,7 @@ def find_by_key(key) # @param [String] url The URL to fetch # @return [String] The content fetched from the URL def fetch(url) - # TODO: support HTTPS too - @open_params ||= begin - u = URL(@root_url) - [u.host, u.port] - end - url = "http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url + url = "#{protocol}://#{open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url request = HTTPI::Request.new(url) if login case auth_type @@ -201,12 +196,7 @@ def fetch(url) end def head(url) - # TODO: support HTTPS too - @open_params ||= begin - u = URL(@root_url) - [u.host, u.port] - end - url = "http://#{@open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url + url = "#{protocol}://#{open_params.join(':')}#{url.gsub(/ /, "%20")}" unless /\Ahttp:\/\// === url request = HTTPI::Request.new(url) if login case auth_type @@ -224,6 +214,20 @@ def head(url) end HTTPI.head(request).headers end + + def open_params + @open_params ||= begin + u = URL(@root_url) + [u.host, u.port] + end + end + + def protocol + @protocol ||= begin + u = URL(@root_url) + u.protocol + end + end end From 5e02bd6afbd9051dc243396428de1fd6e7abbb3b Mon Sep 17 00:00:00 2001 From: France Julien Date: Thu, 20 Feb 2014 13:30:27 +0100 Subject: [PATCH 30/32] Fix for TaxonomyType --- lib/activesp/util.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index b96a07c..73a1330 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -271,6 +271,14 @@ def type_check_attribute(field, value, override_restrictions) end when "ListReference" ActiveSP::List === value and value or raise ArgumentError, "wrong type for #{field.Name} attribute" + when "TaxonomyFieldType" + d = split_multi(value) + # TODO: lookup translated values in metadata store? + d[2] + when "TaxonomyFieldTypeMulti" + d = split_multi(value) + # TODO: lookup translated values in metadata store? + (0...(d.length / 4)).map { |i| d[4 * i + 2] } else raise "not yet #{field.Name}:#{field.internal_type}" end From 73cc178ae7175fbb37239120a2ffc2eef965356d Mon Sep 17 00:00:00 2001 From: France Julien Date: Thu, 20 Feb 2014 13:47:22 +0100 Subject: [PATCH 31/32] Other fix for TaxonomyField --- lib/activesp/util.rb | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/activesp/util.rb b/lib/activesp/util.rb index 73a1330..89618a0 100644 --- a/lib/activesp/util.rb +++ b/lib/activesp/util.rb @@ -272,13 +272,17 @@ def type_check_attribute(field, value, override_restrictions) when "ListReference" ActiveSP::List === value and value or raise ArgumentError, "wrong type for #{field.Name} attribute" when "TaxonomyFieldType" - d = split_multi(value) - # TODO: lookup translated values in metadata store? - d[2] + if value + d = split_multi(value) + # TODO: lookup translated values in metadata store? + d[2] + end when "TaxonomyFieldTypeMulti" - d = split_multi(value) - # TODO: lookup translated values in metadata store? - (0...(d.length / 4)).map { |i| d[4 * i + 2] } + if value + d = split_multi(value) + # TODO: lookup translated values in metadata store? + (0...(d.length / 4)).map { |i| d[4 * i + 2] } + end else raise "not yet #{field.Name}:#{field.internal_type}" end @@ -332,6 +336,18 @@ def untype_cast_attributes(site, list, fields, attributes, override_restrictions end when "ListReference" v = v.ID + when "TaxonomyFieldType" + if v + d = split_multi(v) + # TODO: lookup translated values in metadata store? + v =d[2] + end + when "TaxonomyFieldTypeMulti" + if v + d = split_multi(v) + # TODO: lookup translated values in metadata store? + v = (0...(d.length / 4)).map { |i| d[4 * i + 2] } + end else raise "don't know type #{field.internal_type.inspect} for #{k}=#{v.inspect} on self" end From 461324f508f883da10888af4afa7a233d48a0838 Mon Sep 17 00:00:00 2001 From: Joeri Samson Date: Fri, 4 Sep 2015 13:43:42 +0200 Subject: [PATCH 32/32] Syntax seems to have changed slightly in ruby 2.2 --- lib/activesp/list.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/activesp/list.rb b/lib/activesp/list.rb index 270438a..48a3cd0 100644 --- a/lib/activesp/list.rb +++ b/lib/activesp/list.rb @@ -222,12 +222,13 @@ def changes_since_token(token, options = {}) updates = [] result.xpath("//z:row", NS).each do |row| attributes = clean_item_attributes(row.attributes) - all_attrs = only_attrs ? only_attrs.inject({}) do |h, a| - if attributes.has_key?(a) - h[a] = attributes[a] - end - h - end : attributes + all_attrs = if only_attrs + only_attrs.each_with_object({}) do |a, h| + h[a] = attributes[a] if attributes.has_key?(a) + end + else + attributes + end updates << construct_item(:unset, attributes, no_preload ? nil : all_attrs) end deletes = []