diff --git a/lib/model/generic_folder.rb b/lib/model/generic_folder.rb
index 569d3f6d..2138129b 100644
--- a/lib/model/generic_folder.rb
+++ b/lib/model/generic_folder.rb
@@ -69,7 +69,7 @@ def self.find_folders(root = :msgfolderroot, traversal = 'Shallow', shape = 'Def
if( folder_type.nil? )
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape} )
else
- restr = {:restriction =>
+ restr = {:restriction =>
{:is_equal_to => [{:field_uRI => {:field_uRI=>'folder:FolderClass'}}, {:field_uRI_or_constant=>{:constant => {:value => folder_type}}}]}
}
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder( [normalize_id(root)], traversal, {:base_shape => shape}, restr)
@@ -109,7 +109,7 @@ def self.get_folder_by_name(name, shape = 'Default')
# For now the :field_uRI and :field_uRI_or_constant must be in an Array for Ruby 1.8.7 because Hashes
# are not positional at insertion until 1.9
restr = {:restriction =>
- {:is_equal_to =>
+ {:is_equal_to =>
[{:field_uRI => {:field_uRI=>'folder:DisplayName'}}, {:field_uRI_or_constant =>{:constant => {:value=>name}}}]}}
resp = (Viewpoint::EWS::EWS.instance).ews.find_folder([:msgfolderroot], 'Deep', {:base_shape => shape}, restr)
if(resp.status == 'Success')
@@ -127,12 +127,12 @@ def self.get_folder_by_name(name, shape = 'Default')
def initialize(ews_item)
super() # Calls initialize in Model (creates @ews_methods Array)
- @ews_item = ews_item
- @folder_id = ews_item[:folder_id][:id]
- @ews_methods << :folder_id
- @ews_methods << :id
- @change_key = ews_item[:folder_id][:change_key]
- @ews_methods << :change_key
+ @ews_item = ews_item
+ @folder_id = ews_item[:folder_id][:id]
+ @ews_methods << :folder_id
+ @ews_methods << :id
+ @change_key = ews_item[:folder_id][:change_key]
+ @ews_methods << :change_key
unless ews_item[:parent_folder_id].nil?
@parent_id = ews_item[:parent_folder_id]
@ews_methods << :parent_id
@@ -142,11 +142,12 @@ def initialize(ews_item)
# @todo Handle:
# , , ,
- @sync_state = nil # Base-64 encoded sync data
- @synced = false # Whether or not the synchronization process is complete
- @subscription_id = nil
- @watermark = nil
- @shallow = true
+ @tagspace = (Viewpoint::EWS::EWS.instance).tagspace
+ @sync_state = nil # Base-64 encoded sync data
+ @synced = false # Whether or not the synchronization process is complete
+ @subscription_id = nil
+ @watermark = nil
+ @shallow = true
end
# Subscribe this folder to events. This method initiates an Exchange pull
@@ -217,7 +218,26 @@ def get_events
def find_items(opts = {})
opts = opts.clone # clone the passed in object so we don't modify it in case it's being used in a loop
item_shape = opts.has_key?(:item_shape) ? opts.delete(:item_shape) : {:base_shape => 'Default'}
- item_shape[:additional_properties] = {:field_uRI => ['item:ParentFolderId']}
+ tagspace = opts.delete(:tagspace) || @tagspace
+ if item_shape.has_key?(:additional_properties)
+ aprops = item_shape[:additional_properties]
+ if aprops.has_key?(:field_uRI)
+ raise EwsBadArgumentError, ":field_uRI val should be an Array instead of #{aprops[:field_uRI].class.name}" unless aprops[:field_uRI].is_a?(Array)
+ aprops[:field_uRI] << ['item:ParentFolderId']
+ else
+ aprops[:field_uRI] = ['item:ParentFolderId']
+ end
+ if aprops.has_key?(:extended_field_uRI)
+ raise EwsBadArgumentError, ":extended_field_uRI val should be an Array instead of #{aprops[:extended_field_uRI].class.name}" unless aprops[:extended_field_uRI].is_a?(Array)
+ aprops[:extended_field_uRI] << [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
+ else
+ aprops[:extended_field_uRI] = [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
+ end
+ else
+ item_shape[:additional_properties] = {}
+ item_shape[:additional_properties][:field_uRI] = ['item:ParentFolderId']
+ item_shape[:additional_properties][:extended_field_uRI] = [{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}]
+ end
resp = (Viewpoint::EWS::EWS.instance).ews.find_item([@folder_id], 'Shallow', item_shape, opts)
if(resp.status == 'Success')
parms = resp.items.shift
@@ -232,6 +252,18 @@ def find_items(opts = {})
end
end
+ # Find Items with a specific tag
+ def find_items_with_tag(tag, opts = {})
+ tagspace = opts[:tagspace] || @tagspace
+
+ restrict = { :restriction => {
+ :is_equal_to => [ {:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
+ {:field_uRI_or_constant => {:constant => {:value=>tag}}} ]
+ } }
+
+ find_items(opts.merge(restrict))
+ end
+
# Fetch only items from today (since midnight)
def todays_items(opts = {})
#opts = {:query_string => ["Received:today"]}
@@ -243,7 +275,7 @@ def todays_items(opts = {})
# @param [DateTime] date_time the time to fetch Items since.
def items_since(date_time, opts = {})
restr = {:restriction =>
- {:is_greater_than_or_equal_to =>
+ {:is_greater_than_or_equal_to =>
[{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
{:field_uRI_or_constant =>{:constant => {:value=>date_time}}}]
}}
@@ -255,7 +287,7 @@ def items_since(date_time, opts = {})
# @param [DateTime] end_date the time to stop fetching Items from
def items_between(start_date, end_date, opts={})
restr = {:restriction => {:and => [
- {:is_greater_than_or_equal_to =>
+ {:is_greater_than_or_equal_to =>
[{:field_uRI => {:field_uRI=>'item:DateTimeReceived'}},
{:field_uRI_or_constant=>{:constant => {:value =>start_date}}}]},
{:is_less_than_or_equal_to =>
diff --git a/lib/model/item.rb b/lib/model/item.rb
index ce3e9846..2af23194 100644
--- a/lib/model/item.rb
+++ b/lib/model/item.rb
@@ -57,12 +57,12 @@ def self.add_attachments(parent_id, attachments)
attachments.each do |a|
b64attach << {:name => {:text =>(File.basename a.path)}, :content => {:text => Base64.encode64(a.read)}}
end
- resp = conn.ews.create_attachment(parent_id, b64attach)
+ resp = conn.ews.create_attachment(parent_id, b64attach)
(resp.status == 'Success') || (raise EwsError, "Could not create attachments. #{resp.code}: #{resp.message}")
{:id => resp.items.first[:attachment_id][:root_item_id], :change_key => resp.items.first[:attachment_id][:root_item_change_key]}
end
- attr_reader :item_id, :change_key
+ attr_reader :item_id, :change_key, :tags
alias :id :item_id
# Initialize an Exchange Web Services item
@@ -70,12 +70,14 @@ def self.add_attachments(parent_id, attachments)
# @param [Boolean] shallow Whether or not we have retrieved all the elements for this object
def initialize(ews_item, shallow = true)
super() # Calls initialize in Model (creates @ews_methods Array)
- @ews_item = ews_item
- @shallow = shallow
- @item_id = ews_item[:item_id][:id]
+ @ews_item = ews_item
+ @shallow = shallow
+ @item_id = ews_item[:item_id][:id]
@change_key = ews_item[:item_id][:change_key]
- @text_only = false
- @updates = {}
+ @text_only = false
+ @updates = {}
+ @tags = parse_tags
+ @tagspace = (Viewpoint::EWS::EWS.instance).tagspace
init_methods
end
@@ -182,7 +184,7 @@ def deepen!
return true unless @shallow
conn = Viewpoint::EWS::EWS.instance
shape = {:base_shape => 'AllProperties', :body_type => (@text_only ? 'Text' : 'Best')}
- resp = conn.ews.get_item([@item_id], shape)
+ resp = conn.ews.get_item([@item_id], shape)
resp = resp.items.shift
@ews_item = resp[resp.keys.first]
@shallow = false
@@ -249,7 +251,7 @@ def attachments
end
# Delete this item
- # @param [Boolean] soft Whether or not to do a soft delete. By default EWS will do a
+ # @param [Boolean] soft Whether or not to do a soft delete. By default EWS will do a
# hard delete of this item. See the MSDN docs for more info:
# http://msdn.microsoft.com/en-us/library/aa562961.aspx
# @return [Boolean] Whether or not the item was deleted
@@ -278,7 +280,69 @@ def parent_folder
GenericFolder.get_folder @parent_folder_id
end
+ # Use ExtendedProperties to create tags
+ # @param [String] tag a tag to add to this item
+ # @param [Hash] opts options to pass to add_tag!
+ # @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
+ def add_tag!(tag, opts={})
+ @tags |= [tag.downcase]
+ set_tags!(@tags, opts)
+ end
+
+ # @param [String] tag a tag to delete from this item
+ # @param [Hash] opts options to pass to remove_tag!
+ # @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
+ def remove_tag!(tag, opts={})
+ @tags -= [tag.downcase]
+ if(@tags.blank?)
+ clear_all_tags!(opts)
+ else
+ set_tags!(@tags, opts)
+ end
+ end
+
+ # @param [Hash] opts options to pass to clear_all_tags!
+ # @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
+ def clear_all_tags!(opts={})
+ tagspace = opts[:tagspace] || @tagspace
+ vtag = {:preformatted => []}
+ vtag[:preformatted] << {:delete_item_field =>
+ {:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}}
+ }
+
+ if(self.update_attribs!(vtag))
+ @tags = []
+ true
+ else
+ false
+ end
+ end
+ # @param [Array] tags viewpoint_tags to set on this item
+ # @param [Hash] opts options to pass to set_tags!
+ # @option opts [String] :tagspace the namespace to add the tag to. (default: 'viewpoint_tags')
+ def set_tags!(tags, opts={})
+ tagspace = opts[:tagspace] || @tagspace
+ i_type = self.class.name.split(/::/).last.ruby_case.to_sym
+
+ tag_vals = []
+ tags.each do |t|
+ tag_vals << {:value => {:text => t}}
+ end
+
+ vtag = {:preformatted => []}
+ vtag[:preformatted] << {:set_item_field => [
+ {:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
+ {i_type => [
+ {:extended_property => [
+ {:extended_field_uRI=>{:distinguished_property_set_id=>"PublicStrings", :property_name=>tagspace, :property_type=>"StringArray"}},
+ {:values => tag_vals}
+ ]}
+ ]}
+ ]}
+
+ self.update_attribs!(vtag)
+ end
private
@@ -310,6 +374,26 @@ def method_missing(m, *args, &block)
end
end
+ def parse_tags(opts={})
+ tagspace = opts[:tagspace] || @tagspace
+
+ return [] unless(@ews_item.has_key?(:extended_property) &&
+ @ews_item[:extended_property].has_key?(:extended_field_u_r_i) &&
+ @ews_item[:extended_property][:extended_field_u_r_i].has_key?(:property_name) &&
+ @ews_item[:extended_property][:extended_field_u_r_i][:property_name] == tagspace)
+
+ tags = []
+ vals = @ews_item[:extended_property][:values][:value]
+ if vals.is_a?(Array)
+ vals.each do |v|
+ tags << v[:text]
+ end
+ else
+ tags << vals[:text]
+ end
+ tags
+ end
+
end # Item
end # EWS
end # Viewpoint
diff --git a/lib/soap/handsoap/builders/ews_build_helpers.rb b/lib/soap/handsoap/builders/ews_build_helpers.rb
index 810232d5..bf9e1efb 100644
--- a/lib/soap/handsoap/builders/ews_build_helpers.rb
+++ b/lib/soap/handsoap/builders/ews_build_helpers.rb
@@ -148,6 +148,11 @@ def folder_shape!(node, folder_shape)
end
end
+ # This isn't exactly pretty for AdditionalProperties, but it works. The incoming Hash should be formulated like so:
+ # @example
+ # :additional_properties => {
+ # :extended_field_uRI => [{:distinguished_property_set_id=>"PublicStrings", :property_name=>"viewpoint_tags", :property_type=>"StringArray"}]
+ # }
# @todo Finish AdditionalProperties implementation
def item_shape!(node, item_shape)
node.add("#{NS_EWS_MESSAGES}:ItemShape") do |is|
@@ -157,11 +162,22 @@ def item_shape!(node, item_shape)
is.add("#{NS_EWS_TYPES}:FilterHtmlContent", item_shape[:filter_html_content]) if item_shape.has_key?(:filter_html_content)
is.add("#{NS_EWS_TYPES}:ConvertHtmlCodePageToUTF8", item_shape[:convert_html_code_page_to_utf8]) if item_shape.has_key?(:convert_html_code_page_to_utf8)
unless( item_shape[:additional_properties].nil? )
- unless( item_shape[:additional_properties][:field_uRI].nil? )
- is.add("#{NS_EWS_TYPES}:AdditionalProperties") do |addprops|
- item_shape[:additional_properties][:field_uRI].each do |uri|
- addprops.add("#{NS_EWS_TYPES}:FieldURI") { |furi| furi.set_attr('FieldURI', uri) }
- end
+ is.add("#{NS_EWS_TYPES}:AdditionalProperties") do |addprops|
+ item_shape[:additional_properties].each_pair do |prop_t,prop_v|
+ case prop_t
+ when :field_uRI
+ prop_v.each do |uri|
+ addprops.add("#{NS_EWS_TYPES}:FieldURI") { |furi| furi.set_attr('FieldURI', uri) }
+ end
+ when :extended_field_uRI
+ prop_v.each do |uri|
+ addprops.add("#{NS_EWS_TYPES}:ExtendedFieldURI") do |furi|
+ uri.each_pair do |attr,val|
+ furi.set_attr(attr.to_s.camel_case, val)
+ end
+ end
+ end
+ end #when
end
end
end
diff --git a/lib/viewpoint.rb b/lib/viewpoint.rb
index 00d55f28..878ff77c 100644
--- a/lib/viewpoint.rb
+++ b/lib/viewpoint.rb
@@ -77,11 +77,15 @@ module EWS
# @attr_reader [Viewpoint::EWS::SOAP::ExchangeWebService] :ews The EWS object used
# to make SOAP calls. You typically don't need to use this, but if you want to
# play around with the SOAP back-end it's available.
+ # @attr_accessor [String] :tagspace the name of the tagspace to collect tags in (default: viewpoint_tags)
+ # Set the tagspace used for creating tags on the Model objects. Under the covers this uses an Exchange
+ # StringArray and Exchange extended properties.
class EWS
include Singleton
include Viewpoint
attr_reader :ews
+ attr_accessor :tagspace
# Set the endpoint for Exchange Web Services.
# @param [String] endpoint The URL of the endpoint. This should end in
@@ -122,6 +126,7 @@ def self.set_trust_ca(ca_path)
end
def initialize
+ @tagspace ||= 'viewpoint_tags'
@ews = SOAP::ExchangeWebService.new
end