diff --git a/.gitignore b/.gitignore index 1377554..bcef9c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.swp +.svn/ +pkg/ diff --git a/History.txt b/History.txt index 103a7a6..26b461c 100644 --- a/History.txt +++ b/History.txt @@ -4,3 +4,64 @@ * Birthday! +=== 1.1.0 / 2009-10-26 + +* 2 major enhancements + + * OpenX API Version 2 Support added + * OpenX Invocation API Support added + +* 1 minor enhancement + + * Ruby on Rails documentation support added + +=== 1.1.1 / 2009-11-06 + +* Minor bugfix to environment handling for Win32 users + +=== 1.1.2 / 2009-11-19 + +* Documentation updates and patch to enable banner support on the OpenX Server + +=== 1.1.4 / 2010-04-17 + +* Fork of 1.1.2 + + * Added statistic methods for Banner: + + * renamed statistics method into daily_statistics, left statistics method as an alias for daily_statistics + * daily_statistics(start_on = Date.today, end_on = Date.today) - Daily stats for the Banner + * publisher_statistics(start_on = Date.today, end_on = Date.today) - Banner stats by Publisher + * zone_statistics(start_on = Date.today, end_on = Date.today) - Banner stats by Zone + +=== 1.1.5 / 2010-05-18 + +* Created generic statistics method: + + * get_statistics(service_method, start_on = Date.today, end_on = Date.today, local_time_zone = true) in sepearate module OpenX::Services::Statistics in file statistics.rb + * included statistics module in all stats objects: Agency, Advertiser, Campaign, Banner, Publisher, Zone. + * all existing statistics methods are now calling get_statistics. + +* Added to statistics methods localTZ parameter default to true. This way OpenX will return stats for the period of time with offset according to the local time zone. If that parameter is false it will give numbers for UTC. The localTZ implementation based only on that post http://forum.openx.org/index.php?s=18f1d84f86e32297083f87f8d9eb4fda&showtopic=503433151 + + * statistics + * daily_statistics + * publisher_statistics + * zone_statistics + +* Added statistics methods for other objects as well + + * Agency + * Advertiser + * Campaign + * Banner + * Publisher + + * daily_statistics + * banner_statistics + + * Zone + +=== 1.1.6 / 2010-05-18 + +* lib/openx/services/statistic.rb file was missing on github while building gemspec, so the version 1.1.5 is actually broken. \ No newline at end of file diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..c5807fd --- /dev/null +++ b/README.rdoc @@ -0,0 +1,139 @@ += OpenX + +* http://rubygems.org/gems/jjp-openx +* http://github.com/DoppioJP/openx + +== Description + +A Ruby interface to the OpenX XML-RPC API. Used touchlocal 1.1.2 version as base for adding more API calls to OpenX API from http://developer.openx.org/api/ . It also works with v2 of OpenX API, especially that it now can pass localTZ to the OpenX API which will give back the correct statistics for the local time zone. + +== Synopsis + + OpenX::Services::Base.configuration = { + 'username' => 'admin', + 'password' => 'password', + 'url' => 'http://localhost/www/api/v2/xmlrpc/', + } + + OpenX::Services::Agency.find(:all).each do |agency| + puts agency.name + + # Look up publishers + agency.publishers.each do |publisher| + puts "-- #{publisher.name}" + end + + # Create a publisher + Publisher.create!( + :agency => agency, + :name => 'My Test Publisher', + :contact_name => 'My Contact', + :email => 'agency@example.com', + :username => 'user', + :password => 'password' + ) + end + +== Requirements + +* ruby + +== Install + +* sudo gem install touchlocal-openx --source "http://gemcutter.org" +* add "require 'openx'" to your code +* Update your $HOME/.openx/credentials.yml file. Here is a sample: + + production: + username: admin + password: admin + url: http://www.example.com/www/api/v2/xmlrpc/ + invocation_url: http://www.example.com/www/delivery/axmlrpc.php + +The YAML file lists configuration for each environment. The gem uses the +'production' environment by default. Trailing slash is required on the 'url'. +'invocation_url' is only used by the OpenX::Invocation methods to serve +advertisements over XML-RPC + +== Ruby on Rails Integration + +As common deployment scenarios for RoR projects dictates that you manage all +of your dependent files from within your project, storing your credentials.yml +file in the default location (as above) will not work. However, this is easily +fixed. + +Create your credentials.yml file in your ./config folder, and populate it with +the necessary environment settings. It may in fact be more useful to name your +file something like openx_credentials.yml to be explicit about the content. + +Then, add your gem require line to the initialize block of the environment.rb: + + config.gem "jjp-openx", + :lib => "openx", :source => "http://gemcutter.org" + +You will of course need to install the gem, either manually or via +rake gems:install + +Finally, create a config/initializers/openx.rb and include the following: + + require 'yaml' + OpenX::Services::Base.configuration = + YAML.load_file(File.join(Rails.root, 'config', 'credentials.yml'))[Rails.env] + + +== Banner Keyword Support + +Not all attributes of OpenX objects that can be set in the web interface are +accessible via the API. A notable case of this is the ability to access +Banner Keywords. While it seems to be a case of "We'll add them as people +need them," this process is slower than one might expect; +https://developer.openx.org/jira/browse/OX-4779 has been an open ticket since +January 2009. + +As TouchLocal required the ability to access Banner Keywords, the +OpenX::Services::Banner object has the support for this attribute. If the +server does not support the attribute, then setting it will have no effect, +and it will return nil when retrieving it. To enable support on the server, +you will need access to the server source, and the included +php/openx-2.8.1-keywords.diff + +Copy the openx-2.8.1-keywords.diff file to the root of the OpenX distribution +on your server, and execute: + + patch -p0 < openx-2.8.1-keywords.diff + +This will patch the relevant OpenX API to allow access to the keyword +attribute, meaning that now both your client and server support it. Happy days. + +This patch has been tested on OpenX Server versions 2.8.1 and 2.8.2 at the +time of writing. + + +== License + +(The MIT License) + +Copyright (c) 2008: + +* {Aaron Patterson}[http://tenderlovemaking.com] +* Andy Smith +* {TouchLocal Ltd}[http://www.touchlocal.com] + +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. diff --git a/README.txt b/README.txt deleted file mode 100644 index 1b3547e..0000000 --- a/README.txt +++ /dev/null @@ -1,80 +0,0 @@ -= openx - -* http://openx.rubyforge.org - -== DESCRIPTION: - -A Ruby interface to the OpenX XML-RPC API. - -== SYNOPSIS: - - OpenX::Services::Base.configuration = { - 'username' => 'admin', - 'password' => 'password', - 'url' => 'http://localhost/www/api/v2/xmlrpc/', - } - - OpenX::Services::Agency.find(:all).each do |agency| - puts agency.name - - # Look up publishers - agency.publishers.each do |publisher| - puts "-- #{publisher.name}" - end - - # Create a publisher - Publisher.create!( - :agency => agency, - :name => 'My Test Publisher', - :contact_name => 'Aaron Patterson', - :email => 'aaron@tenderlovemaking.com', - :username => 'user', - :password => 'password' - ) - end - -== REQUIREMENTS: - -* ruby - -== INSTALL: - -* sudo gem install openx -* Update your $HOME/.openx/credentials.yml file. Here is a sample: - - --- - production: - username: admin - password: admin - url: http://localhost/~asmith/openx/www/api/v2/xmlrpc/ - -The YAML file lists configuration for each environment. The gem uses the -'production' environment by default. - -== LICENSE: - -(The MIT License) - -Copyright (c) 2008: - -* {Aaron Patterson}[http://tenderlovemaking.com] -* Andy Smith - -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. diff --git a/Rakefile b/Rakefile index c179232..475d93c 100644 --- a/Rakefile +++ b/Rakefile @@ -1,16 +1,26 @@ # -*- ruby -*- require 'rubygems' -require 'hoe' +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |gemspec| + gemspec.name = "jjp-openx" + gemspec.summary = "A Ruby interface to the OpenX XML-RPC API with more OpenX APIs used" + gemspec.description = "A Ruby interface to the OpenX XML-RPC API. Used touchlocal 1.1.2 version as base for adding more API calls to OpenX API from http://developer.openx.org/api/ . It also works with v2 of OpenX API, especially that it now can pass localTZ to the OpenX API which will give back the correct statistics for the local time zone." + gemspec.email = "jacobjp@mac.com" + gemspec.homepage = "http://github.com/DoppioJP/openx" + gemspec.authors = ["Aaron Patterson", "Andy Smith", "TouchLocal Plc", "DoppioJP"] + end + Jeweler::GemcutterTasks.new +rescue LoadError + puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" +end $: << "lib/" require 'openx' -HOE = Hoe.new('openx', OpenX::VERSION) do |p| - # p.rubyforge_name = 'ruby-openxx' # if different than lowercase project name - p.developer('Aaron Patterson', 'aaron.patterson@gmail.com') -end - namespace :openx do task :clean do include OpenX::Services @@ -28,13 +38,3 @@ namespace :openx do end end end - -namespace :gem do - task :spec do - File.open("#{HOE.name}.gemspec", 'w') do |f| - f.write(HOE.spec.to_ruby) - end - end -end - -# vim: syntax=Ruby diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0664a8f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.6 diff --git a/jjp-openx.gemspec b/jjp-openx.gemspec new file mode 100644 index 0000000..dd9f99d --- /dev/null +++ b/jjp-openx.gemspec @@ -0,0 +1,79 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE +# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec` +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{jjp-openx} + s.version = "1.1.5" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Aaron Patterson", "Andy Smith", "TouchLocal Plc", "DoppioJP"] + s.date = %q{2010-05-19} + s.description = %q{A Ruby interface to the OpenX XML-RPC API. Used touchlocal 1.1.2 version as base for adding more API calls to OpenX API from http://developer.openx.org/api/ . It also works with v2 of OpenX API, especially that it now can pass localTZ to the OpenX API which will give back the correct statistics for the local time zone.} + s.email = %q{jacobjp@mac.com} + s.extra_rdoc_files = [ + "README.rdoc" + ] + s.files = [ + ".gitignore", + "History.txt", + "Manifest.txt", + "README.rdoc", + "Rakefile", + "VERSION", + "jjp-openx.gemspec", + "lib/openx.rb", + "lib/openx/image.rb", + "lib/openx/invocation.rb", + "lib/openx/services.rb", + "lib/openx/services/advertiser.rb", + "lib/openx/services/agency.rb", + "lib/openx/services/banner.rb", + "lib/openx/services/base.rb", + "lib/openx/services/campaign.rb", + "lib/openx/services/publisher.rb", + "lib/openx/services/session.rb", + "lib/openx/services/statistics.rb", + "lib/openx/services/zone.rb", + "lib/openx/xmlrpc_client.rb", + "php/openx-2.8.1-keywords.diff", + "test/assets/300x250.jpg", + "test/assets/cat.swf", + "test/helper.rb", + "test/test_advertiser.rb", + "test/test_agency.rb", + "test/test_banner.rb", + "test/test_base.rb", + "test/test_campaign.rb", + "test/test_publisher.rb", + "test/test_session.rb", + "test/test_zone.rb" + ] + s.homepage = %q{http://github.com/DoppioJP/openx} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.6} + s.summary = %q{A Ruby interface to the OpenX XML-RPC API with more OpenX APIs used} + s.test_files = [ + "test/helper.rb", + "test/test_advertiser.rb", + "test/test_agency.rb", + "test/test_banner.rb", + "test/test_base.rb", + "test/test_campaign.rb", + "test/test_publisher.rb", + "test/test_session.rb", + "test/test_zone.rb" + ] + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + else + end + else + end +end diff --git a/lib/openx.rb b/lib/openx.rb index 4a01b8a..1fc8240 100644 --- a/lib/openx.rb +++ b/lib/openx.rb @@ -1,7 +1,9 @@ require 'xmlrpc/client' +require 'openx/xmlrpc_client' require 'openx/services' +require 'openx/invocation' require 'openx/image' module OpenX - VERSION = '1.0.0' + VERSION = '1.1.0' end diff --git a/lib/openx/invocation.rb b/lib/openx/invocation.rb new file mode 100644 index 0000000..5ff1fb0 --- /dev/null +++ b/lib/openx/invocation.rb @@ -0,0 +1,77 @@ +module OpenX + class Invocation + class << self + # Whatever + # banner = OpenX::Invocation.view("Plumber") + # + # banners = OpenX::Invocation.view("Plumber", :count => 2, :exclude_by_campaignid => true) ;nil + # + # banners.each do |banner| + # puts "Banner #{banner['bannerid']}" + # end; nil + # + # + # Defaults + # :count => 1, + # :campaignid => 0, + # :target => '', + # :source => '', + # :with_text => false, + # :exclusions => [], + # :inclusions => [], + # :exclude_by_campaignid => false, + # :exclude_by_bannerid => false + # + def view(what, params = {}) + defaults = { + :count => 1, + :campaignid => 0, + :target => '', + :source => '', + :with_text => false, + :exclusions => [], + :inclusions => [], + :exclude_by_campaignid => false, + :exclude_by_bannerid => false + } + params = defaults.merge(params) + + url = OpenX::Services::Base.configuration['invocation_url'] + + # Basic requirement as per /lib/max/Delivery/XML-RPC.php in OpenX + settings = {:cookies => [], :remote_addr => 'localhost'} + + context = [] # used by reference after initial use + params[:exclusions].each { |item| context << convert_to_context(false, item) } + params[:inclusions].each { |item| context << convert_to_context(true, item) } + count = params[:count].to_i + + remote_params = [ what, params[:campaignid], params[:target], params[:source], params[:with_text], context] + server = XmlrpcClient.new2(url) + + out = [] + if count > 0 + (0...count).each do + out << banner = server.call('openads.view', settings, *remote_params) + if count > 1 + if params[:exclude_by_campaignid] + campaign_id = banner['campaignid'] + context << convert_to_context(false, "campaignid:#{campaign_id}") + elsif params[:exclude_by_bannerid] + banner_id = banner['bannerid'] + context << convert_to_context(false, "bannerid:#{banner_id}") + end + end + end + end + count > 1 ? out : out.first + end + + def convert_to_context(is_inclusion, item) + key = is_inclusion ? '==' : '!=' + { key => item } + end + protected :convert_to_context + end + end +end diff --git a/lib/openx/services/advertiser.rb b/lib/openx/services/advertiser.rb index 37148c8..eb378dd 100644 --- a/lib/openx/services/advertiser.rb +++ b/lib/openx/services/advertiser.rb @@ -1,6 +1,10 @@ module OpenX module Services class Advertiser < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + openx_accessor :name => :advertiserName, :contact_name => :contactName, :email => :emailAddress, diff --git a/lib/openx/services/agency.rb b/lib/openx/services/agency.rb index 5157336..781c310 100644 --- a/lib/openx/services/agency.rb +++ b/lib/openx/services/agency.rb @@ -1,6 +1,10 @@ module OpenX module Services class Agency < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + # Translate our property names to OpenX property names openx_accessor :name => :agencyName, :contact_name => :contactName, diff --git a/lib/openx/services/banner.rb b/lib/openx/services/banner.rb index 6319faa..7ad678c 100644 --- a/lib/openx/services/banner.rb +++ b/lib/openx/services/banner.rb @@ -3,6 +3,10 @@ module OpenX module Services class Banner < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + LOCAL_SQL = 'sql' LOCAL_WEB = 'web' EXTERNAL = 'url' @@ -11,13 +15,13 @@ class Banner < Base RUNNING = 0 PAUSED = 1 - + class << self def find(id, *args) session = self.connection - server = XMLRPC::Client.new2("#{session.url}") + server = XmlrpcClient.new2("#{session.url}") if id == :all - responses = server.call(find_all(), session.id, *args) + responses = server.call(find_all(), session, *args) response = responses.first return [] unless response responses = [response] @@ -34,7 +38,7 @@ def find(id, *args) new(translate(response)) } else - response = server.call(find_one(), session.id, id) + response = server.call(find_one(), session, id) new(translate(response)) end end @@ -57,7 +61,11 @@ def find(id, *args) :adserver => :adserver, :transparent => :transparent, :image => :aImage, - :backup_image => :aBackupImage + :backup_image => :aBackupImage, + # 'keyword' only supported by patched server + # as per README.rdoc + # No averse effect when unsupported by server (returns nil) + :keyword => :keyword has_one :campaign @@ -68,24 +76,44 @@ def find(id, *args) self.find_all = 'ox.getBannerListByCampaignId' def initialize(params = {}) - raise ArgumentError unless params[:campaign_id] || params[:campaign] + raise ArgumentError.new("Missing campaign_id") unless params[:campaign_id] || params[:campaign] params[:campaign_id] ||= params[:campaign].id super(params) end - def statistics start_on = Date.today, end_on = Date.today - session = self.class.connection - @server.call('ox.bannerDailyStatistics', session.id, self.id, start_on, end_on) + # Alias for daily_statistics method to keep consistency with OpenX API calls. + # Originally it was call for ox.bannerDailyStatistics so it is kept for compatibility with the previous version of the gem. + def statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + daily_statistics start_on, end_on, local_time_zone + end + + # Returns statistics in Array of Hashes by day, which are: impressions, clicks, requests and revenue. + # Each day is represented by XMLRPC::DateTime which has instant variables: + # @year, @month, @day, @hour, @min, @sec + def daily_statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + self.get_statistics('ox.bannerDailyStatistics', start_on, end_on, local_time_zone) + end + + # Returns statistics in Array of Hashes by publisher, which are: impression, clicks, requests and revenue. + # Also returns publisherName and publisherId + def publisher_statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + self.get_statistics('ox.bannerPublisherStatistics', start_on, end_on, local_time_zone) + end + + # Returns statistics in Array of Hashes by zone, which are: impression, clicks, requests, conversions and revenue. + # Also returns publisherName, publisherId, zoneName, zoneId + def zone_statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + self.get_statistics('ox.bannerZoneStatistics', start_on, end_on, local_time_zone) end def targeting session = self.class.connection - @server.call('ox.getBannerTargeting', session.id, self.id) + @server.call('ox.getBannerTargeting', session, self.id) end def targeting= targeting session = self.class.connection - @server.call('ox.setBannerTargeting', session.id, self.id, targeting) + @server.call('ox.setBannerTargeting', session, self.id, targeting) end end end diff --git a/lib/openx/services/base.rb b/lib/openx/services/base.rb index 4ae4a9d..56db799 100644 --- a/lib/openx/services/base.rb +++ b/lib/openx/services/base.rb @@ -5,7 +5,17 @@ module Services class Base include Comparable - CONFIGURATION_YAML = File.join(ENV['HOME'], '.openx', 'credentials.yml') + # HOME || HOMEPATH is for Win32 users who do not have HOME set by default + # + # Configuration can be overridden for Rails at load time by doing this: + # OpenX::Services::Base.configuration = + # YAML.load_file(File.join(Rails.root, 'config', 'credentials.yml'))[Rails.env] + # + # Rescue nil is there for Rails sites that are monitored by Monit, + # which does not set the environment variables as expected. + # Note that the configuration can be set explicitly (as above), + # in which case this constant is not used and can safely be nil. + CONFIGURATION_YAML = File.join((ENV['HOME'] || ENV['HOMEPATH']), '.openx', 'credentials.yml') rescue nil @@connection = nil @@configuration = nil @@ -60,14 +70,14 @@ def has_one(*things) def find(id, *args) session = self.connection - server = XMLRPC::Client.new2("#{session.url}") + server = XmlrpcClient.new2("#{session.url}") if id == :all - responses = server.call(find_all(), session.id, *args) + responses = server.call(find_all(), session, *args) responses.map { |response| new(translate(response)) } else - response = server.call(find_one(), session.id, id) + response = server.call(find_one(), session, id) new(translate(response)) end end @@ -89,8 +99,7 @@ def translate(response) def initialize(params = {}) @id = nil params.each { |k,v| send(:"#{k}=", v) } - @server = XMLRPC::Client.new2("#{self.class.connection.url}") - #@server.instance_variable_get(:@http).set_debug_output($stderr) + @server = XmlrpcClient.new2("#{self.class.connection.url}") end def new_record?; @id.nil?; end @@ -104,16 +113,16 @@ def save! } if new_record? - @id = @server.call(self.class.create, session.id, params) + @id = @server.call(self.class.create, session, params) else - @server.call(self.class.update, session.id, params) + @server.call(self.class.update, session, params) end self end def destroy session = self.class.connection - @server.call(self.class.delete, session.id, id) + @server.call(self.class.delete, session, id) @id = nil end diff --git a/lib/openx/services/campaign.rb b/lib/openx/services/campaign.rb index 17bb72e..00af096 100644 --- a/lib/openx/services/campaign.rb +++ b/lib/openx/services/campaign.rb @@ -1,6 +1,10 @@ module OpenX module Services class Campaign < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + # Translate our property names to OpenX property names openx_accessor :name => :campaignName, :advertiser_id => :advertiserId, @@ -27,7 +31,7 @@ class Campaign < Base self.find_all = 'ox.getCampaignListByAdvertiserId' # Revenue types - CPM = 1 + CPM = 1 CPC = 2 CPA = 3 MONTHLY_TENANCY = 4 @@ -37,12 +41,14 @@ class Campaign < Base HIGH = 2 EXCLUSIVE = 3 + # Creates new Campaign for the given Advertiser.id required in params[:advertiser_id] or params[:advertiser] def initialize(params = {}) - raise ArgumentError unless params[:advertiser_id] || params[:advertiser] + raise ArgumentError.new("Missing advertiser_id") unless params[:advertiser_id] || params[:advertiser] params[:advertiser_id] ||= params[:advertiser].id super(params) end + # Return all banners for the Campaign def banners Banner.find(:all, self.id) end diff --git a/lib/openx/services/publisher.rb b/lib/openx/services/publisher.rb index ae97252..fa92779 100644 --- a/lib/openx/services/publisher.rb +++ b/lib/openx/services/publisher.rb @@ -1,6 +1,10 @@ module OpenX module Services class Publisher < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + openx_accessor :name => :publisherName, :contact_name => :contactName, :email => :emailAddress, @@ -26,6 +30,18 @@ def initialize(params = {}) def zones Zone.find(:all, self.id) end + + # Returns statistics in Array of Hashes by day, which are: impression, clicks, requests and revenue. + def daily_statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + self.get_statistics('ox.publisherDailyStatistics', start_on, end_on, local_time_zone) + end + + # Returns statistics in Array of Hashes by banner, which are: impression, clicks, requests and revenue. + # Also returns bannerName, bannerId, advertiserName, advertiserId, campaignName, campaignId + def banner_statistics start_on = Date.today, end_on = Date.today, local_time_zone = true + self.get_statistics('ox.publisherBannerStatistics', start_on, end_on, local_time_zone) + end + end end end diff --git a/lib/openx/services/session.rb b/lib/openx/services/session.rb index bb5fbcb..fd31d29 100644 --- a/lib/openx/services/session.rb +++ b/lib/openx/services/session.rb @@ -2,15 +2,24 @@ module OpenX module Services class Session attr_accessor :url, :id + attr_accessor :user, :password def initialize(url) @url = url - @server = XMLRPC::Client.new2("#{@url}") + @server = XmlrpcClient.new2("#{@url}") @id = nil end def create(user, password) - @id = @server.call('ox.logon', user, password) + @user = user + @password = password + @id = @server.call('ox.logon', @user, @password) + self + end + + def recreate! + raise "Unable to refresh Session" unless @user && @password + @id = @server.call('ox.logon', @user, @password) self end diff --git a/lib/openx/services/statistics.rb b/lib/openx/services/statistics.rb new file mode 100644 index 0000000..f003e1c --- /dev/null +++ b/lib/openx/services/statistics.rb @@ -0,0 +1,25 @@ +module OpenX + module Services + module Statistics + + # Generic method to get statistics. + # - +service_method+ - The name of the OpenX API service method to be called. + # - +start_on+ - When date range starts for stats request. OpenX parameter +oStartDate+ which ignores time part of it. + # - +end_on+ - When date range ends for stats request. OpenX parameter +oEndDate+ which ignores time part of it. + # - +local_time_zone+ - For v2 of OpenX API only which will respect the local Time Zone. OpenX parameter +localTZ+. + # + # With such generic method the gem is able to get any statistics available from the OpenX API, + # because all those statistics methods have the same call format. + def get_statistics service_method, start_on = Date.today, end_on = Date.today, local_time_zone = true + session = self.class.connection + + # For compatibility with v1 of OpenX API. + if OpenX::Services::Base.configuration["url"].include?("/v1/") + @server.call(service_method, session, self.id, start_on, end_on) + else + @server.call(service_method, session, self.id, start_on, end_on, local_time_zone) + end + end + end + end +end \ No newline at end of file diff --git a/lib/openx/services/zone.rb b/lib/openx/services/zone.rb index af60a1c..67a3b6a 100644 --- a/lib/openx/services/zone.rb +++ b/lib/openx/services/zone.rb @@ -1,6 +1,10 @@ module OpenX module Services class Zone < Base + + require 'openx/services/statistics' + include OpenX::Services::Statistics + # Delivery types BANNER = 'delivery-b' INTERSTITIAL = 'delivery-i' @@ -33,7 +37,7 @@ class << self # Deliver +zone_id+ to +ip_address+ with +cookies+, def deliver zone_id, ip_address = '192.168.1.1', cookies = [] url = "#{self.configuration['root']}/delivery/axmlrpc.php" - server = XMLRPC::Client.new2(url) + server = XmlrpcClient.new2(url) server.call('openads.view', { 'cookies' => cookies, 'remote_addr' => ip_address, @@ -53,8 +57,8 @@ def link_campaign(campaign) raise ArgumentError.new("Campaign must be saved")if campaign.new_record? session = self.class.connection - server = XMLRPC::Client.new2("#{session.url}") - server.call("ox.linkCampaign", session.id, self.id, campaign.id) + server = XmlrpcClient.new2("#{session.url}") + server.call("ox.linkCampaign", session, self.id, campaign.id) end # Unlink this zone from +campaign+ @@ -63,8 +67,8 @@ def unlink_campaign(campaign) raise ArgumentError.new("Campaign must be saved")if campaign.new_record? session = self.class.connection - server = XMLRPC::Client.new2("#{session.url}") - server.call("ox.unlinkCampaign", session.id, self.id, campaign.id) + server = XmlrpcClient.new2("#{session.url}") + server.call("ox.unlinkCampaign", session, self.id, campaign.id) end # Link this zone to +banner+ @@ -73,8 +77,8 @@ def link_banner(banner) raise ArgumentError.new("Banner must be saved")if banner.new_record? session = self.class.connection - server = XMLRPC::Client.new2("#{session.url}") - server.call("ox.linkBanner", session.id, self.id, banner.id) + server = XmlrpcClient.new2("#{session.url}") + server.call("ox.linkBanner", session, self.id, banner.id) end # Unlink this zone from +banner+ @@ -83,15 +87,15 @@ def unlink_banner(banner) raise ArgumentError.new("Banner must be saved")if banner.new_record? session = self.class.connection - server = XMLRPC::Client.new2("#{session.url}") - server.call("ox.unlinkBanner", session.id, self.id, banner.id) + server = XmlrpcClient.new2("#{session.url}") + server.call("ox.unlinkBanner", session, self.id, banner.id) end # Generate tags for displaying this zone using +tag_type+ def generate_tags(tag_type = IFRAME) session = self.class.connection - server = XMLRPC::Client.new2("#{session.url}") - server.call("ox.generateTags", session.id, self.id, tag_type, []) + server = XmlrpcClient.new2("#{session.url}") + server.call("ox.generateTags", session, self.id, tag_type, []) end end end diff --git a/lib/openx/xmlrpc_client.rb b/lib/openx/xmlrpc_client.rb new file mode 100644 index 0000000..1fd4fad --- /dev/null +++ b/lib/openx/xmlrpc_client.rb @@ -0,0 +1,78 @@ +require 'xmlrpc/client' + +module OpenX + + unless defined? HTTPBroken + # A module that captures all the possible Net::HTTP exceptions + # from http://pastie.org/pastes/145154 + module HTTPBroken; end + + [Timeout::Error, Errno::EINVAL, Errno::EPIPE, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, + Net::HTTPHeaderSyntaxError, Net::ProtocolError].each {|m| m.send(:include, HTTPBroken)} + end + + class XmlrpcClient + @uri = nil + @server = nil + + @retry_on_http_error = true + @timeout = 10 # seconds + + class << self + attr_accessor :retry_on_http_error, :timeout + + def init_server(uri) + server = XMLRPC::Client.new2(uri) + server.timeout = self.timeout + #server.instance_variable_get(:@http).set_debug_output($stderr) + server + end + + def new2(uri) + server = init_server(uri) + new(server, uri) + end + end + + def initialize(server, uri) + @server = server + @uri = uri + end + + def call(method, *args) + if args.first.is_a?(OpenX::Services::Session) + session = args.shift() + args.unshift(session.id) + begin + do_call(method, *args) + rescue XMLRPC::FaultException => sess_id_err + if sess_id_err.message.strip == 'Session ID is invalid' + session.recreate! + args.shift() + args.unshift(session.id) + do_call(method, *args) + else + raise sess_id_err + end + end + else + do_call(method, *args) + end + end + + def do_call(method, *args) + begin + @server.call(method, *args) + rescue HTTPBroken => httpbe + if self.class.retry_on_http_error + @server = self.class.init_server(@uri) + @server.call(method, *args) + else + raise httpbe + end + end + end + private :do_call + + end +end diff --git a/php/openx-2.8.1-keywords.diff b/php/openx-2.8.1-keywords.diff new file mode 100644 index 0000000..823dfce --- /dev/null +++ b/php/openx-2.8.1-keywords.diff @@ -0,0 +1,110 @@ +Index: www/api/v2/xmlrpc/BannerXmlRpcService.php +=================================================================== +--- www/api/v2/xmlrpc/BannerXmlRpcService.php (revision 44144) ++++ www/api/v2/xmlrpc/BannerXmlRpcService.php (working copy) +@@ -86,7 +86,7 @@ + 1, array('campaignId', 'bannerName', 'storageType', 'fileName', + 'imageURL', 'htmlTemplate', 'width', 'height', 'weight', + 'target', 'url', 'bannerText', 'status', 'adserver', 'transparent', +- 'capping', 'sessionCapping', 'block', 'comments'), ++ 'capping', 'sessionCapping', 'block', 'comments', 'keyword'), + array('aImage', 'aBackupImage'), $oResponseWithError)) { + + return $oResponseWithError; +@@ -121,7 +121,7 @@ + 1, array('bannerId', 'campaignId', 'bannerName', 'storageType', 'fileName', + 'imageURL', 'htmlTemplate', 'width', 'height', 'weight', + 'target', 'url', 'bannerText', 'status', 'adserver', 'transparent', +- 'capping', 'sessionCapping', 'block', 'comments'), ++ 'capping', 'sessionCapping', 'block', 'comments', 'keyword'), + array('aImage', 'aBackupImage'), $oResponseWithError)) { + + return $oResponseWithError; +Index: lib/OA/Dll/Banner.php +=================================================================== +--- lib/OA/Dll/Banner.php (revision 44144) ++++ lib/OA/Dll/Banner.php (working copy) +@@ -203,7 +203,8 @@ + !$this->checkStructureNotRequiredIntegerField($oBanner, 'capping') || + !$this->checkStructureNotRequiredIntegerField($oBanner, 'sessionCapping') || + !$this->checkStructureNotRequiredIntegerField($oBanner, 'block') || +- !$this->checkStructureNotRequiredStringField($oBanner, 'comments') ++ !$this->checkStructureNotRequiredStringField($oBanner, 'comments') || ++ !$this->checkStructureNotRequiredStringField($oBanner, 'keyword') + ) { + return false; + } +Index: lib/OA/Dll/BannerInfo.php +=================================================================== +--- lib/OA/Dll/BannerInfo.php (revision 44144) ++++ lib/OA/Dll/BannerInfo.php (working copy) +@@ -210,6 +210,13 @@ + var $comments; + + /** ++ * This field provides keywords to be stored. ++ * ++ * @var string $keyword ++ */ ++ var $keyword; ++ ++ /** + * This method sets all default values when adding a new banner. + * + * @access public +@@ -251,6 +258,10 @@ + if (is_null($this->block)) { + // Leave null + } ++ ++ if (is_null($this->keyword)) { ++ // Leave null ++ } + } + + function encodeImage($aImage) +@@ -305,6 +316,7 @@ + 'aImage' => 'custom', + 'aBackupImage' => 'custom', + 'comments' => 'string', ++ 'keyword' => 'string', + ); + } + } +Index: lib/xmlrpc/php/BannerInfo.php +=================================================================== +--- lib/xmlrpc/php/BannerInfo.php (revision 44144) ++++ lib/xmlrpc/php/BannerInfo.php (working copy) +@@ -210,6 +210,13 @@ + var $comments; + + /** ++ * This field provides keywords to be stored. ++ * ++ * @var string $keyword ++ */ ++ var $keyword; ++ ++ /** + * This method sets all default values when adding a new banner. + * + * @access public +@@ -251,6 +258,10 @@ + if (is_null($this->block)) { + // Leave null + } ++ ++ if (is_null($this->keyword)) { ++ // Leave null ++ } + } + + function encodeImage($aImage) +@@ -305,6 +316,7 @@ + 'aImage' => 'custom', + 'aBackupImage' => 'custom', + 'comments' => 'string', ++ 'keyword' => 'string', + ); + } + }