entp-ruby-openid 2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data/CHANGELOG +215 -0
  2. data/INSTALL +47 -0
  3. data/LICENSE +210 -0
  4. data/NOTICE +2 -0
  5. data/README +85 -0
  6. data/UPGRADE +127 -0
  7. data/admin/runtests.rb +45 -0
  8. data/examples/README +32 -0
  9. data/examples/active_record_openid_store/README +58 -0
  10. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  11. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  12. data/examples/active_record_openid_store/init.rb +8 -0
  13. data/examples/active_record_openid_store/lib/association.rb +10 -0
  14. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  15. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  16. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  17. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  18. data/examples/discover +49 -0
  19. data/examples/rails_openid/README +153 -0
  20. data/examples/rails_openid/Rakefile +10 -0
  21. data/examples/rails_openid/app/controllers/application.rb +4 -0
  22. data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
  23. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  24. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  25. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  26. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  27. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  28. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  29. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  30. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  31. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  32. data/examples/rails_openid/config/boot.rb +19 -0
  33. data/examples/rails_openid/config/database.yml +74 -0
  34. data/examples/rails_openid/config/environment.rb +54 -0
  35. data/examples/rails_openid/config/environments/development.rb +19 -0
  36. data/examples/rails_openid/config/environments/production.rb +19 -0
  37. data/examples/rails_openid/config/environments/test.rb +19 -0
  38. data/examples/rails_openid/config/routes.rb +24 -0
  39. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  40. data/examples/rails_openid/public/404.html +8 -0
  41. data/examples/rails_openid/public/500.html +8 -0
  42. data/examples/rails_openid/public/dispatch.cgi +12 -0
  43. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  44. data/examples/rails_openid/public/dispatch.rb +12 -0
  45. data/examples/rails_openid/public/favicon.ico +0 -0
  46. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  47. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  48. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  49. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  50. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  51. data/examples/rails_openid/public/robots.txt +1 -0
  52. data/examples/rails_openid/script/about +3 -0
  53. data/examples/rails_openid/script/breakpointer +3 -0
  54. data/examples/rails_openid/script/console +3 -0
  55. data/examples/rails_openid/script/destroy +3 -0
  56. data/examples/rails_openid/script/generate +3 -0
  57. data/examples/rails_openid/script/performance/benchmarker +3 -0
  58. data/examples/rails_openid/script/performance/profiler +3 -0
  59. data/examples/rails_openid/script/plugin +3 -0
  60. data/examples/rails_openid/script/process/reaper +3 -0
  61. data/examples/rails_openid/script/process/spawner +3 -0
  62. data/examples/rails_openid/script/process/spinner +3 -0
  63. data/examples/rails_openid/script/runner +3 -0
  64. data/examples/rails_openid/script/server +3 -0
  65. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  66. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  67. data/examples/rails_openid/test/test_helper.rb +28 -0
  68. data/lib/hmac/hmac.rb +112 -0
  69. data/lib/hmac/sha1.rb +11 -0
  70. data/lib/hmac/sha2.rb +25 -0
  71. data/lib/openid.rb +22 -0
  72. data/lib/openid/association.rb +249 -0
  73. data/lib/openid/consumer.rb +395 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +497 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +150 -0
  81. data/lib/openid/cryptutil.rb +115 -0
  82. data/lib/openid/dh.rb +89 -0
  83. data/lib/openid/extension.rb +39 -0
  84. data/lib/openid/extensions/ax.rb +539 -0
  85. data/lib/openid/extensions/oauth.rb +91 -0
  86. data/lib/openid/extensions/pape.rb +179 -0
  87. data/lib/openid/extensions/sreg.rb +277 -0
  88. data/lib/openid/extras.rb +11 -0
  89. data/lib/openid/fetchers.rb +258 -0
  90. data/lib/openid/kvform.rb +136 -0
  91. data/lib/openid/kvpost.rb +58 -0
  92. data/lib/openid/message.rb +553 -0
  93. data/lib/openid/protocolerror.rb +12 -0
  94. data/lib/openid/server.rb +1544 -0
  95. data/lib/openid/store.rb +10 -0
  96. data/lib/openid/store/filesystem.rb +272 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +109 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +119 -0
  104. data/lib/openid/version.rb +3 -0
  105. data/lib/openid/yadis.rb +15 -0
  106. data/lib/openid/yadis/accept.rb +148 -0
  107. data/lib/openid/yadis/constants.rb +21 -0
  108. data/lib/openid/yadis/discovery.rb +153 -0
  109. data/lib/openid/yadis/filters.rb +205 -0
  110. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  111. data/lib/openid/yadis/parsehtml.rb +45 -0
  112. data/lib/openid/yadis/services.rb +42 -0
  113. data/lib/openid/yadis/xrds.rb +155 -0
  114. data/lib/openid/yadis/xri.rb +90 -0
  115. data/lib/openid/yadis/xrires.rb +91 -0
  116. data/test/data/test_discover/openid_utf8.html +11 -0
  117. data/test/support/test_data_mixin.rb +127 -0
  118. data/test/support/test_util.rb +53 -0
  119. data/test/support/yadis_data.rb +131 -0
  120. data/test/support/yadis_data/accept.txt +124 -0
  121. data/test/support/yadis_data/dh.txt +29 -0
  122. data/test/support/yadis_data/example-xrds.xml +14 -0
  123. data/test/support/yadis_data/linkparse.txt +587 -0
  124. data/test/support/yadis_data/n2b64 +650 -0
  125. data/test/support/yadis_data/test1-discover.txt +137 -0
  126. data/test/support/yadis_data/test1-parsehtml.txt +152 -0
  127. data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/support/yadis_data/test_discover/openid.html +11 -0
  129. data/test/support/yadis_data/test_discover/openid2.html +11 -0
  130. data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
  138. data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
  144. data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/support/yadis_data/test_xrds/README +12 -0
  148. data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
  152. data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
  153. data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
  155. data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
  157. data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
  158. data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
  159. data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
  160. data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
  161. data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/support/yadis_data/trustroot.txt +153 -0
  163. data/test/support/yadis_data/urinorm.txt +79 -0
  164. data/test/test_accept.rb +170 -0
  165. data/test/test_association.rb +268 -0
  166. data/test/test_associationmanager.rb +918 -0
  167. data/test/test_ax.rb +690 -0
  168. data/test/test_checkid_request.rb +293 -0
  169. data/test/test_consumer.rb +260 -0
  170. data/test/test_cryptutil.rb +119 -0
  171. data/test/test_dh.rb +85 -0
  172. data/test/test_discover.rb +848 -0
  173. data/test/test_discovery_manager.rb +259 -0
  174. data/test/test_extension.rb +46 -0
  175. data/test/test_extras.rb +35 -0
  176. data/test/test_fetchers.rb +554 -0
  177. data/test/test_filters.rb +269 -0
  178. data/test/test_helper.rb +4 -0
  179. data/test/test_idres.rb +961 -0
  180. data/test/test_kvform.rb +164 -0
  181. data/test/test_kvpost.rb +64 -0
  182. data/test/test_linkparse.rb +100 -0
  183. data/test/test_message.rb +1115 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +176 -0
  186. data/test/test_openid_yadis.rb +177 -0
  187. data/test/test_pape.rb +248 -0
  188. data/test/test_parsehtml.rb +79 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2455 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +292 -0
  193. data/test/test_trustroot.rb +111 -0
  194. data/test/test_urinorm.rb +34 -0
  195. data/test/test_util.rb +145 -0
  196. data/test/test_xrds.rb +167 -0
  197. data/test/test_xri.rb +48 -0
  198. data/test/test_xrires.rb +67 -0
  199. data/test/test_yadis_discovery.rb +218 -0
  200. metadata +268 -0
@@ -0,0 +1,56 @@
1
+
2
+
3
+ <% if session[:username].nil? %>
4
+
5
+ <div id="login-form">
6
+ <form method="get" action="<%= url_for :controller => 'login', :action => 'submit' %>">
7
+ Type a username:
8
+ <input type="text" name="username" />
9
+ <input type="submit" value="Log In" />
10
+ </form>
11
+
12
+ </div>
13
+
14
+ <% end %>
15
+
16
+ <p> Welcome to the Ruby OpenID example. This code is a starting point
17
+ for developers wishing to implement an OpenID provider or relying
18
+ party. We've used the <a href="http://rubyonrails.org/">Rails</a>
19
+ platform to demonstrate, but the library code is not Rails specific.</p>
20
+
21
+ <h2>To use the example provider</h2>
22
+ <p>
23
+ <ol>
24
+
25
+ <li>Enter a username in the form above. You will be "Logged In"
26
+ to the server, at which point you may authenticate using an OpenID
27
+ consumer. Your OpenID URL will be displayed after you log
28
+ in.<p>The server will automatically create an identity page for
29
+ you at <%= @base_url %>user/<i>name</i></p></li>
30
+
31
+ <li><p>Because WEBrick can only handle one thing at a time, you'll need to
32
+ run another instance of the example on another port if you want to use
33
+ a relying party to use with this example provider:</p>
34
+ <blockquote>
35
+ <code>script/server --port=3001</code>
36
+ </blockquote>
37
+
38
+ <p>(The RP needs to be able to access the provider, so unless you're
39
+ running this example on a public IP, you can't use the live example
40
+ at <a href="http://openidenabled.com/">openidenabled.com</a> on
41
+ your local provider.)</p>
42
+ </li>
43
+
44
+ <li>Point your browser to this new instance and follow the directions
45
+ below.</li>
46
+ <!-- Fun fact: 'url_for :port => 3001' doesn't work very well. -->
47
+ </ol>
48
+
49
+ </p>
50
+
51
+ <h2>To use the example relying party</h2>
52
+
53
+ <p>Visit <a href="<%= url_for :controller => 'consumer' %>">/consumer</a>
54
+ and enter your OpenID.</p>
55
+ </p>
56
+
@@ -0,0 +1,26 @@
1
+ <form method="post" action="<%= url_for :controller => 'server', :action => 'decision' %>">
2
+
3
+ <table>
4
+ <tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>
5
+
6
+ <% if @oidreq.id_select %>
7
+ <tr>
8
+ <td colspan="2">
9
+ You entered the server identifier at the relying party.
10
+ You'll need to send an identifier of your choosing. Enter a
11
+ username below.
12
+ </td>
13
+ </tr>
14
+ <tr>
15
+ <td>Identity to send:</td>
16
+ <td><input type="text" name="id_to_send" size="25" /></td>
17
+ </tr>
18
+ <% else %>
19
+ <tr><td>Identity:</td><td><%= @oidreq.identity %></td></tr>
20
+ <% end %>
21
+ </table>
22
+
23
+ <input type="submit" name="yes" value="yes" />
24
+ <input type="submit" name="no" value="no" />
25
+
26
+ </form>
@@ -0,0 +1,19 @@
1
+ # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
2
+
3
+ unless defined?(RAILS_ROOT)
4
+ root_path = File.join(File.dirname(__FILE__), '..')
5
+ unless RUBY_PLATFORM =~ /mswin32/
6
+ require 'pathname'
7
+ root_path = Pathname.new(root_path).cleanpath(true).to_s
8
+ end
9
+ RAILS_ROOT = root_path
10
+ end
11
+
12
+ if File.directory?("#{RAILS_ROOT}/vendor/rails")
13
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
14
+ else
15
+ require 'rubygems'
16
+ require 'initializer'
17
+ end
18
+
19
+ Rails::Initializer.run(:set_load_path)
@@ -0,0 +1,74 @@
1
+ # MySQL (default setup). Versions 4.1 and 5.0 are recommended.
2
+ #
3
+ # Get the fast C bindings:
4
+ # gem install mysql
5
+ # (on OS X: gem install mysql -- --include=/usr/local/lib)
6
+ # And be sure to use new-style password hashing:
7
+ # http://dev.mysql.com/doc/refman/5.0/en/old-client.html
8
+ development:
9
+ adapter: sqlite3
10
+ database: db/development.sqlite3
11
+
12
+ # Warning: The database defined as 'test' will be erased and
13
+ # re-generated from your development database when you run 'rake'.
14
+ # Do not set this db to the same as development or production.
15
+ test:
16
+ adapter: sqlite3
17
+ database: ":memory:"
18
+
19
+ production:
20
+ adapter: mysql
21
+ database: rails_server_production
22
+ username: root
23
+ password:
24
+ socket: /path/to/your/mysql.sock
25
+
26
+
27
+ # PostgreSQL versions 7.4 - 8.1
28
+ #
29
+ # Get the C bindings:
30
+ # gem install postgres
31
+ # or use the pure-Ruby bindings on Windows:
32
+ # gem install postgres-pr
33
+ postgresql_example:
34
+ adapter: postgresql
35
+ database: rails_server_development
36
+ username: rails_server
37
+ password:
38
+
39
+ # Connect on a TCP socket. Omitted by default since the client uses a
40
+ # domain socket that doesn't need configuration.
41
+ #host: remote-database
42
+ #port: 5432
43
+
44
+ # Schema search path. The server defaults to $user,public
45
+ #schema_search_path: myapp,sharedapp,public
46
+
47
+ # Character set encoding. The server defaults to sql_ascii.
48
+ #encoding: UTF8
49
+
50
+ # Minimum log levels, in increasing order:
51
+ # debug5, debug4, debug3, debug2, debug1,
52
+ # info, notice, warning, error, log, fatal, or panic
53
+ # The server defaults to notice.
54
+ #min_messages: warning
55
+
56
+
57
+ # SQLite version 2.x
58
+ # gem install sqlite-ruby
59
+ sqlite_example:
60
+ adapter: sqlite
61
+ database: db/development.sqlite2
62
+
63
+
64
+ # SQLite version 3.x
65
+ # gem install sqlite3-ruby
66
+ sqlite3_example:
67
+ adapter: sqlite3
68
+ database: db/development.sqlite3
69
+
70
+
71
+ # In-memory SQLite 3 database. Useful for tests.
72
+ sqlite3_in_memory_example:
73
+ adapter: sqlite3
74
+ database: ":memory:"
@@ -0,0 +1,54 @@
1
+ # Be sure to restart your web server when you modify this file.
2
+
3
+ # Uncomment below to force Rails into production mode when
4
+ # you don't control web/app server and can't set it the proper way
5
+ # ENV['RAILS_ENV'] ||= 'production'
6
+
7
+ # Bootstrap the Rails environment, frameworks, and default configuration
8
+ require File.join(File.dirname(__FILE__), 'boot')
9
+
10
+ Rails::Initializer.run do |config|
11
+ # Settings in config/environments/* take precedence those specified here
12
+
13
+ # Skip frameworks you're not going to use
14
+ # config.frameworks -= [ :action_web_service, :action_mailer ]
15
+
16
+ # Add additional load paths for your own custom dirs
17
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
18
+
19
+ # Force all environments to use the same logger level
20
+ # (by default production uses :info, the others :debug)
21
+ # config.log_level = :debug
22
+
23
+ # Use the database for sessions instead of the file system
24
+ # (create the session table with 'rake create_sessions_table')
25
+ # config.action_controller.session_store = :active_record_store
26
+
27
+ # Enable page/fragment caching by setting a file-based store
28
+ # (remember to create the caching directory and make it readable to the application)
29
+ # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
30
+
31
+ # Activate observers that should always be running
32
+ # config.active_record.observers = :cacher, :garbage_collector
33
+
34
+ # Make Active Record use UTC-base instead of local time
35
+ # config.active_record.default_timezone = :utc
36
+
37
+ # Use Active Record's schema dumper instead of SQL when creating the test database
38
+ # (enables use of different database adapters for development and test environments)
39
+ # config.active_record.schema_format = :ruby
40
+
41
+ # See Rails::Configuration for more options
42
+ end
43
+
44
+ # Add new inflection rules using the following format
45
+ # (all these examples are active by default):
46
+ # Inflector.inflections do |inflect|
47
+ # inflect.plural /^(ox)$/i, '\1en'
48
+ # inflect.singular /^(ox)en/i, '\1'
49
+ # inflect.irregular 'person', 'people'
50
+ # inflect.uncountable %w( fish sheep )
51
+ # end
52
+
53
+ # Include your application configuration below
54
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = '_session_id_2'
@@ -0,0 +1,19 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # In the development environment your application's code is reloaded on
4
+ # every request. This slows down response time but is perfect for development
5
+ # since you don't have to restart the webserver when you make code changes.
6
+ config.cache_classes = false
7
+
8
+ # Log error messages when you accidentally call methods on nil.
9
+ config.whiny_nils = true
10
+
11
+ # Enable the breakpoint server that script/breakpointer connects to
12
+ config.breakpoint_server = true
13
+
14
+ # Show full error reports and disable caching
15
+ config.action_controller.consider_all_requests_local = true
16
+ config.action_controller.perform_caching = false
17
+
18
+ # Don't care if the mailer can't send
19
+ config.action_mailer.raise_delivery_errors = false
@@ -0,0 +1,19 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # The production environment is meant for finished, "live" apps.
4
+ # Code is not reloaded between requests
5
+ config.cache_classes = true
6
+
7
+ # Use a different logger for distributed setups
8
+ # config.logger = SyslogLogger.new
9
+
10
+
11
+ # Full error reports are disabled and caching is turned on
12
+ config.action_controller.consider_all_requests_local = false
13
+ config.action_controller.perform_caching = true
14
+
15
+ # Enable serving of images, stylesheets, and javascripts from an asset server
16
+ # config.action_controller.asset_host = "http://assets.example.com"
17
+
18
+ # Disable delivery errors if you bad email addresses should just be ignored
19
+ # config.action_mailer.raise_delivery_errors = false
@@ -0,0 +1,19 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+
3
+ # The test environment is used exclusively to run your application's
4
+ # test suite. You never need to work with it otherwise. Remember that
5
+ # your test database is "scratch space" for the test suite and is wiped
6
+ # and recreated between test runs. Don't rely on the data there!
7
+ config.cache_classes = true
8
+
9
+ # Log error messages when you accidentally call methods on nil.
10
+ config.whiny_nils = true
11
+
12
+ # Show full error reports and disable caching
13
+ config.action_controller.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
15
+
16
+ # Tell ActionMailer not to deliver emails to the real world.
17
+ # The :test delivery method accumulates sent emails in the
18
+ # ActionMailer::Base.deliveries array.
19
+ config.action_mailer.delivery_method = :test
@@ -0,0 +1,24 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ # Add your own custom routes here.
3
+ # The priority is based upon order of creation: first created -> highest priority.
4
+
5
+ # Here's a sample route:
6
+ # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7
+ # Keep in mind you can assign values other than :controller and :action
8
+
9
+ # You can have the root of your site routed by hooking up ''
10
+ # -- just remember to delete public/index.html.
11
+ # map.connect '', :controller => "welcome"
12
+
13
+ map.connect '', :controller => 'login'
14
+ map.connect 'server/xrds', :controller => 'server', :action => 'idp_xrds'
15
+ map.connect 'user/:username', :controller => 'server', :action => 'user_page'
16
+ map.connect 'user/:username/xrds', :controller => 'server', :action => 'user_xrds'
17
+
18
+ # Allow downloading Web Service WSDL as a file with an extension
19
+ # instead of a file named 'wsdl'
20
+ map.connect ':controller/service.wsdl', :action => 'wsdl'
21
+
22
+ # Install the default route as the lowest priority.
23
+ map.connect ':controller/:action/:id'
24
+ end
@@ -0,0 +1,2 @@
1
+ Use this README file to introduce your application and point to useful places in the API for learning more.
2
+ Run "rake appdoc" to generate API documentation for your models and controllers.
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
3
+ <html>
4
+ <body>
5
+ <h1>File not found</h1>
6
+ <p>Change this error message for pages not found in public/404.html</p>
7
+ </body>
8
+ </html>
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
3
+ <html>
4
+ <body>
5
+ <h1>Application error (Apache)</h1>
6
+ <p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
7
+ </body>
8
+ </html>
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/ruby1.8
2
+
3
+ #!/usr/local/bin/ruby
4
+
5
+ require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
6
+
7
+ # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
8
+ # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
9
+ require "dispatcher"
10
+
11
+ ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
12
+ Dispatcher.dispatch
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/ruby1.8
2
+
3
+ #!/usr/local/bin/ruby
4
+ #
5
+ # You may specify the path to the FastCGI crash log (a log of unhandled
6
+ # exceptions which forced the FastCGI instance to exit, great for debugging)
7
+ # and the number of requests to process before running garbage collection.
8
+ #
9
+ # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
10
+ # and the GC period is nil (turned off). A reasonable number of requests
11
+ # could range from 10-100 depending on the memory footprint of your app.
12
+ #
13
+ # Example:
14
+ # # Default log path, normal GC behavior.
15
+ # RailsFCGIHandler.process!
16
+ #
17
+ # # Default log path, 50 requests between GC.
18
+ # RailsFCGIHandler.process! nil, 50
19
+ #
20
+ # # Custom log path, normal GC behavior.
21
+ # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
22
+ #
23
+ require File.dirname(__FILE__) + "/../config/environment"
24
+ require 'fcgi_handler'
25
+
26
+ RailsFCGIHandler.process!
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/ruby1.8
2
+
3
+ #!/usr/local/bin/ruby
4
+
5
+ require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
6
+
7
+ # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
8
+ # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
9
+ require "dispatcher"
10
+
11
+ ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
12
+ Dispatcher.dispatch
@@ -0,0 +1,750 @@
1
+ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
+ // (c) 2005 Jon Tirsen (http://www.tirsen.com)
4
+ // Contributors:
5
+ // Richard Livsey
6
+ // Rahul Bhargava
7
+ // Rob Wills
8
+ //
9
+ // See scriptaculous.js for full license.
10
+
11
+ // Autocompleter.Base handles all the autocompletion functionality
12
+ // that's independent of the data source for autocompletion. This
13
+ // includes drawing the autocompletion menu, observing keyboard
14
+ // and mouse events, and similar.
15
+ //
16
+ // Specific autocompleters need to provide, at the very least,
17
+ // a getUpdatedChoices function that will be invoked every time
18
+ // the text inside the monitored textbox changes. This method
19
+ // should get the text for which to provide autocompletion by
20
+ // invoking this.getToken(), NOT by directly accessing
21
+ // this.element.value. This is to allow incremental tokenized
22
+ // autocompletion. Specific auto-completion logic (AJAX, etc)
23
+ // belongs in getUpdatedChoices.
24
+ //
25
+ // Tokenized incremental autocompletion is enabled automatically
26
+ // when an autocompleter is instantiated with the 'tokens' option
27
+ // in the options parameter, e.g.:
28
+ // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
29
+ // will incrementally autocomplete with a comma as the token.
30
+ // Additionally, ',' in the above example can be replaced with
31
+ // a token array, e.g. { tokens: [',', '\n'] } which
32
+ // enables autocompletion on multiple tokens. This is most
33
+ // useful when one of the tokens is \n (a newline), as it
34
+ // allows smart autocompletion after linebreaks.
35
+
36
+ var Autocompleter = {}
37
+ Autocompleter.Base = function() {};
38
+ Autocompleter.Base.prototype = {
39
+ baseInitialize: function(element, update, options) {
40
+ this.element = $(element);
41
+ this.update = $(update);
42
+ this.hasFocus = false;
43
+ this.changed = false;
44
+ this.active = false;
45
+ this.index = 0;
46
+ this.entryCount = 0;
47
+
48
+ if (this.setOptions)
49
+ this.setOptions(options);
50
+ else
51
+ this.options = options || {};
52
+
53
+ this.options.paramName = this.options.paramName || this.element.name;
54
+ this.options.tokens = this.options.tokens || [];
55
+ this.options.frequency = this.options.frequency || 0.4;
56
+ this.options.minChars = this.options.minChars || 1;
57
+ this.options.onShow = this.options.onShow ||
58
+ function(element, update){
59
+ if(!update.style.position || update.style.position=='absolute') {
60
+ update.style.position = 'absolute';
61
+ Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
62
+ }
63
+ Effect.Appear(update,{duration:0.15});
64
+ };
65
+ this.options.onHide = this.options.onHide ||
66
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
67
+
68
+ if (typeof(this.options.tokens) == 'string')
69
+ this.options.tokens = new Array(this.options.tokens);
70
+
71
+ this.observer = null;
72
+
73
+ this.element.setAttribute('autocomplete','off');
74
+
75
+ Element.hide(this.update);
76
+
77
+ Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
78
+ Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
79
+ },
80
+
81
+ show: function() {
82
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
83
+ if(!this.iefix &&
84
+ (navigator.appVersion.indexOf('MSIE')>0) &&
85
+ (navigator.userAgent.indexOf('Opera')<0) &&
86
+ (Element.getStyle(this.update, 'position')=='absolute')) {
87
+ new Insertion.After(this.update,
88
+ '<iframe id="' + this.update.id + '_iefix" '+
89
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
90
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
91
+ this.iefix = $(this.update.id+'_iefix');
92
+ }
93
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
94
+ },
95
+
96
+ fixIEOverlapping: function() {
97
+ Position.clone(this.update, this.iefix);
98
+ this.iefix.style.zIndex = 1;
99
+ this.update.style.zIndex = 2;
100
+ Element.show(this.iefix);
101
+ },
102
+
103
+ hide: function() {
104
+ this.stopIndicator();
105
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
106
+ if(this.iefix) Element.hide(this.iefix);
107
+ },
108
+
109
+ startIndicator: function() {
110
+ if(this.options.indicator) Element.show(this.options.indicator);
111
+ },
112
+
113
+ stopIndicator: function() {
114
+ if(this.options.indicator) Element.hide(this.options.indicator);
115
+ },
116
+
117
+ onKeyPress: function(event) {
118
+ if(this.active)
119
+ switch(event.keyCode) {
120
+ case Event.KEY_TAB:
121
+ case Event.KEY_RETURN:
122
+ this.selectEntry();
123
+ Event.stop(event);
124
+ case Event.KEY_ESC:
125
+ this.hide();
126
+ this.active = false;
127
+ Event.stop(event);
128
+ return;
129
+ case Event.KEY_LEFT:
130
+ case Event.KEY_RIGHT:
131
+ return;
132
+ case Event.KEY_UP:
133
+ this.markPrevious();
134
+ this.render();
135
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
136
+ return;
137
+ case Event.KEY_DOWN:
138
+ this.markNext();
139
+ this.render();
140
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
141
+ return;
142
+ }
143
+ else
144
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145
+ return;
146
+
147
+ this.changed = true;
148
+ this.hasFocus = true;
149
+
150
+ if(this.observer) clearTimeout(this.observer);
151
+ this.observer =
152
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
+ },
154
+
155
+ onHover: function(event) {
156
+ var element = Event.findElement(event, 'LI');
157
+ if(this.index != element.autocompleteIndex)
158
+ {
159
+ this.index = element.autocompleteIndex;
160
+ this.render();
161
+ }
162
+ Event.stop(event);
163
+ },
164
+
165
+ onClick: function(event) {
166
+ var element = Event.findElement(event, 'LI');
167
+ this.index = element.autocompleteIndex;
168
+ this.selectEntry();
169
+ this.hide();
170
+ },
171
+
172
+ onBlur: function(event) {
173
+ // needed to make click events working
174
+ setTimeout(this.hide.bind(this), 250);
175
+ this.hasFocus = false;
176
+ this.active = false;
177
+ },
178
+
179
+ render: function() {
180
+ if(this.entryCount > 0) {
181
+ for (var i = 0; i < this.entryCount; i++)
182
+ this.index==i ?
183
+ Element.addClassName(this.getEntry(i),"selected") :
184
+ Element.removeClassName(this.getEntry(i),"selected");
185
+
186
+ if(this.hasFocus) {
187
+ this.show();
188
+ this.active = true;
189
+ }
190
+ } else {
191
+ this.active = false;
192
+ this.hide();
193
+ }
194
+ },
195
+
196
+ markPrevious: function() {
197
+ if(this.index > 0) this.index--
198
+ else this.index = this.entryCount-1;
199
+ },
200
+
201
+ markNext: function() {
202
+ if(this.index < this.entryCount-1) this.index++
203
+ else this.index = 0;
204
+ },
205
+
206
+ getEntry: function(index) {
207
+ return this.update.firstChild.childNodes[index];
208
+ },
209
+
210
+ getCurrentEntry: function() {
211
+ return this.getEntry(this.index);
212
+ },
213
+
214
+ selectEntry: function() {
215
+ this.active = false;
216
+ this.updateElement(this.getCurrentEntry());
217
+ },
218
+
219
+ updateElement: function(selectedElement) {
220
+ if (this.options.updateElement) {
221
+ this.options.updateElement(selectedElement);
222
+ return;
223
+ }
224
+
225
+ var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
226
+ var lastTokenPos = this.findLastToken();
227
+ if (lastTokenPos != -1) {
228
+ var newValue = this.element.value.substr(0, lastTokenPos + 1);
229
+ var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
230
+ if (whitespace)
231
+ newValue += whitespace[0];
232
+ this.element.value = newValue + value;
233
+ } else {
234
+ this.element.value = value;
235
+ }
236
+ this.element.focus();
237
+
238
+ if (this.options.afterUpdateElement)
239
+ this.options.afterUpdateElement(this.element, selectedElement);
240
+ },
241
+
242
+ updateChoices: function(choices) {
243
+ if(!this.changed && this.hasFocus) {
244
+ this.update.innerHTML = choices;
245
+ Element.cleanWhitespace(this.update);
246
+ Element.cleanWhitespace(this.update.firstChild);
247
+
248
+ if(this.update.firstChild && this.update.firstChild.childNodes) {
249
+ this.entryCount =
250
+ this.update.firstChild.childNodes.length;
251
+ for (var i = 0; i < this.entryCount; i++) {
252
+ var entry = this.getEntry(i);
253
+ entry.autocompleteIndex = i;
254
+ this.addObservers(entry);
255
+ }
256
+ } else {
257
+ this.entryCount = 0;
258
+ }
259
+
260
+ this.stopIndicator();
261
+
262
+ this.index = 0;
263
+ this.render();
264
+ }
265
+ },
266
+
267
+ addObservers: function(element) {
268
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
269
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
270
+ },
271
+
272
+ onObserverEvent: function() {
273
+ this.changed = false;
274
+ if(this.getToken().length>=this.options.minChars) {
275
+ this.startIndicator();
276
+ this.getUpdatedChoices();
277
+ } else {
278
+ this.active = false;
279
+ this.hide();
280
+ }
281
+ },
282
+
283
+ getToken: function() {
284
+ var tokenPos = this.findLastToken();
285
+ if (tokenPos != -1)
286
+ var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
287
+ else
288
+ var ret = this.element.value;
289
+
290
+ return /\n/.test(ret) ? '' : ret;
291
+ },
292
+
293
+ findLastToken: function() {
294
+ var lastTokenPos = -1;
295
+
296
+ for (var i=0; i<this.options.tokens.length; i++) {
297
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
298
+ if (thisTokenPos > lastTokenPos)
299
+ lastTokenPos = thisTokenPos;
300
+ }
301
+ return lastTokenPos;
302
+ }
303
+ }
304
+
305
+ Ajax.Autocompleter = Class.create();
306
+ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
307
+ initialize: function(element, update, url, options) {
308
+ this.baseInitialize(element, update, options);
309
+ this.options.asynchronous = true;
310
+ this.options.onComplete = this.onComplete.bind(this);
311
+ this.options.defaultParams = this.options.parameters || null;
312
+ this.url = url;
313
+ },
314
+
315
+ getUpdatedChoices: function() {
316
+ entry = encodeURIComponent(this.options.paramName) + '=' +
317
+ encodeURIComponent(this.getToken());
318
+
319
+ this.options.parameters = this.options.callback ?
320
+ this.options.callback(this.element, entry) : entry;
321
+
322
+ if(this.options.defaultParams)
323
+ this.options.parameters += '&' + this.options.defaultParams;
324
+
325
+ new Ajax.Request(this.url, this.options);
326
+ },
327
+
328
+ onComplete: function(request) {
329
+ this.updateChoices(request.responseText);
330
+ }
331
+
332
+ });
333
+
334
+ // The local array autocompleter. Used when you'd prefer to
335
+ // inject an array of autocompletion options into the page, rather
336
+ // than sending out Ajax queries, which can be quite slow sometimes.
337
+ //
338
+ // The constructor takes four parameters. The first two are, as usual,
339
+ // the id of the monitored textbox, and id of the autocompletion menu.
340
+ // The third is the array you want to autocomplete from, and the fourth
341
+ // is the options block.
342
+ //
343
+ // Extra local autocompletion options:
344
+ // - choices - How many autocompletion choices to offer
345
+ //
346
+ // - partialSearch - If false, the autocompleter will match entered
347
+ // text only at the beginning of strings in the
348
+ // autocomplete array. Defaults to true, which will
349
+ // match text at the beginning of any *word* in the
350
+ // strings in the autocomplete array. If you want to
351
+ // search anywhere in the string, additionally set
352
+ // the option fullSearch to true (default: off).
353
+ //
354
+ // - fullSsearch - Search anywhere in autocomplete array strings.
355
+ //
356
+ // - partialChars - How many characters to enter before triggering
357
+ // a partial match (unlike minChars, which defines
358
+ // how many characters are required to do any match
359
+ // at all). Defaults to 2.
360
+ //
361
+ // - ignoreCase - Whether to ignore case when autocompleting.
362
+ // Defaults to true.
363
+ //
364
+ // It's possible to pass in a custom function as the 'selector'
365
+ // option, if you prefer to write your own autocompletion logic.
366
+ // In that case, the other options above will not apply unless
367
+ // you support them.
368
+
369
+ Autocompleter.Local = Class.create();
370
+ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
371
+ initialize: function(element, update, array, options) {
372
+ this.baseInitialize(element, update, options);
373
+ this.options.array = array;
374
+ },
375
+
376
+ getUpdatedChoices: function() {
377
+ this.updateChoices(this.options.selector(this));
378
+ },
379
+
380
+ setOptions: function(options) {
381
+ this.options = Object.extend({
382
+ choices: 10,
383
+ partialSearch: true,
384
+ partialChars: 2,
385
+ ignoreCase: true,
386
+ fullSearch: false,
387
+ selector: function(instance) {
388
+ var ret = []; // Beginning matches
389
+ var partial = []; // Inside matches
390
+ var entry = instance.getToken();
391
+ var count = 0;
392
+
393
+ for (var i = 0; i < instance.options.array.length &&
394
+ ret.length < instance.options.choices ; i++) {
395
+
396
+ var elem = instance.options.array[i];
397
+ var foundPos = instance.options.ignoreCase ?
398
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
399
+ elem.indexOf(entry);
400
+
401
+ while (foundPos != -1) {
402
+ if (foundPos == 0 && elem.length != entry.length) {
403
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
404
+ elem.substr(entry.length) + "</li>");
405
+ break;
406
+ } else if (entry.length >= instance.options.partialChars &&
407
+ instance.options.partialSearch && foundPos != -1) {
408
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
409
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
410
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
411
+ foundPos + entry.length) + "</li>");
412
+ break;
413
+ }
414
+ }
415
+
416
+ foundPos = instance.options.ignoreCase ?
417
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
418
+ elem.indexOf(entry, foundPos + 1);
419
+
420
+ }
421
+ }
422
+ if (partial.length)
423
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
424
+ return "<ul>" + ret.join('') + "</ul>";
425
+ }
426
+ }, options || {});
427
+ }
428
+ });
429
+
430
+ // AJAX in-place editor
431
+ //
432
+ // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
433
+
434
+ // Use this if you notice weird scrolling problems on some browsers,
435
+ // the DOM might be a bit confused when this gets called so do this
436
+ // waits 1 ms (with setTimeout) until it does the activation
437
+ Field.scrollFreeActivate = function(field) {
438
+ setTimeout(function() {
439
+ Field.activate(field);
440
+ }, 1);
441
+ }
442
+
443
+ Ajax.InPlaceEditor = Class.create();
444
+ Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
445
+ Ajax.InPlaceEditor.prototype = {
446
+ initialize: function(element, url, options) {
447
+ this.url = url;
448
+ this.element = $(element);
449
+
450
+ this.options = Object.extend({
451
+ okText: "ok",
452
+ cancelText: "cancel",
453
+ savingText: "Saving...",
454
+ clickToEditText: "Click to edit",
455
+ okText: "ok",
456
+ rows: 1,
457
+ onComplete: function(transport, element) {
458
+ new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
459
+ },
460
+ onFailure: function(transport) {
461
+ alert("Error communicating with the server: " + transport.responseText.stripTags());
462
+ },
463
+ callback: function(form) {
464
+ return Form.serialize(form);
465
+ },
466
+ handleLineBreaks: true,
467
+ loadingText: 'Loading...',
468
+ savingClassName: 'inplaceeditor-saving',
469
+ loadingClassName: 'inplaceeditor-loading',
470
+ formClassName: 'inplaceeditor-form',
471
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
472
+ highlightendcolor: "#FFFFFF",
473
+ externalControl: null,
474
+ ajaxOptions: {}
475
+ }, options || {});
476
+
477
+ if(!this.options.formId && this.element.id) {
478
+ this.options.formId = this.element.id + "-inplaceeditor";
479
+ if ($(this.options.formId)) {
480
+ // there's already a form with that name, don't specify an id
481
+ this.options.formId = null;
482
+ }
483
+ }
484
+
485
+ if (this.options.externalControl) {
486
+ this.options.externalControl = $(this.options.externalControl);
487
+ }
488
+
489
+ this.originalBackground = Element.getStyle(this.element, 'background-color');
490
+ if (!this.originalBackground) {
491
+ this.originalBackground = "transparent";
492
+ }
493
+
494
+ this.element.title = this.options.clickToEditText;
495
+
496
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
497
+ this.mouseoverListener = this.enterHover.bindAsEventListener(this);
498
+ this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
499
+ Event.observe(this.element, 'click', this.onclickListener);
500
+ Event.observe(this.element, 'mouseover', this.mouseoverListener);
501
+ Event.observe(this.element, 'mouseout', this.mouseoutListener);
502
+ if (this.options.externalControl) {
503
+ Event.observe(this.options.externalControl, 'click', this.onclickListener);
504
+ Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
505
+ Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
506
+ }
507
+ },
508
+ enterEditMode: function(evt) {
509
+ if (this.saving) return;
510
+ if (this.editing) return;
511
+ this.editing = true;
512
+ this.onEnterEditMode();
513
+ if (this.options.externalControl) {
514
+ Element.hide(this.options.externalControl);
515
+ }
516
+ Element.hide(this.element);
517
+ this.createForm();
518
+ this.element.parentNode.insertBefore(this.form, this.element);
519
+ Field.scrollFreeActivate(this.editField);
520
+ // stop the event to avoid a page refresh in Safari
521
+ if (evt) {
522
+ Event.stop(evt);
523
+ }
524
+ return false;
525
+ },
526
+ createForm: function() {
527
+ this.form = document.createElement("form");
528
+ this.form.id = this.options.formId;
529
+ Element.addClassName(this.form, this.options.formClassName)
530
+ this.form.onsubmit = this.onSubmit.bind(this);
531
+
532
+ this.createEditField();
533
+
534
+ if (this.options.textarea) {
535
+ var br = document.createElement("br");
536
+ this.form.appendChild(br);
537
+ }
538
+
539
+ okButton = document.createElement("input");
540
+ okButton.type = "submit";
541
+ okButton.value = this.options.okText;
542
+ this.form.appendChild(okButton);
543
+
544
+ cancelLink = document.createElement("a");
545
+ cancelLink.href = "#";
546
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
547
+ cancelLink.onclick = this.onclickCancel.bind(this);
548
+ this.form.appendChild(cancelLink);
549
+ },
550
+ hasHTMLLineBreaks: function(string) {
551
+ if (!this.options.handleLineBreaks) return false;
552
+ return string.match(/<br/i) || string.match(/<p>/i);
553
+ },
554
+ convertHTMLLineBreaks: function(string) {
555
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
556
+ },
557
+ createEditField: function() {
558
+ var text;
559
+ if(this.options.loadTextURL) {
560
+ text = this.options.loadingText;
561
+ } else {
562
+ text = this.getText();
563
+ }
564
+
565
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
566
+ this.options.textarea = false;
567
+ var textField = document.createElement("input");
568
+ textField.type = "text";
569
+ textField.name = "value";
570
+ textField.value = text;
571
+ textField.style.backgroundColor = this.options.highlightcolor;
572
+ var size = this.options.size || this.options.cols || 0;
573
+ if (size != 0) textField.size = size;
574
+ this.editField = textField;
575
+ } else {
576
+ this.options.textarea = true;
577
+ var textArea = document.createElement("textarea");
578
+ textArea.name = "value";
579
+ textArea.value = this.convertHTMLLineBreaks(text);
580
+ textArea.rows = this.options.rows;
581
+ textArea.cols = this.options.cols || 40;
582
+ this.editField = textArea;
583
+ }
584
+
585
+ if(this.options.loadTextURL) {
586
+ this.loadExternalText();
587
+ }
588
+ this.form.appendChild(this.editField);
589
+ },
590
+ getText: function() {
591
+ return this.element.innerHTML;
592
+ },
593
+ loadExternalText: function() {
594
+ Element.addClassName(this.form, this.options.loadingClassName);
595
+ this.editField.disabled = true;
596
+ new Ajax.Request(
597
+ this.options.loadTextURL,
598
+ Object.extend({
599
+ asynchronous: true,
600
+ onComplete: this.onLoadedExternalText.bind(this)
601
+ }, this.options.ajaxOptions)
602
+ );
603
+ },
604
+ onLoadedExternalText: function(transport) {
605
+ Element.removeClassName(this.form, this.options.loadingClassName);
606
+ this.editField.disabled = false;
607
+ this.editField.value = transport.responseText.stripTags();
608
+ },
609
+ onclickCancel: function() {
610
+ this.onComplete();
611
+ this.leaveEditMode();
612
+ return false;
613
+ },
614
+ onFailure: function(transport) {
615
+ this.options.onFailure(transport);
616
+ if (this.oldInnerHTML) {
617
+ this.element.innerHTML = this.oldInnerHTML;
618
+ this.oldInnerHTML = null;
619
+ }
620
+ return false;
621
+ },
622
+ onSubmit: function() {
623
+ // onLoading resets these so we need to save them away for the Ajax call
624
+ var form = this.form;
625
+ var value = this.editField.value;
626
+
627
+ // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
628
+ // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
629
+ // to be displayed indefinitely
630
+ this.onLoading();
631
+
632
+ new Ajax.Updater(
633
+ {
634
+ success: this.element,
635
+ // don't update on failure (this could be an option)
636
+ failure: null
637
+ },
638
+ this.url,
639
+ Object.extend({
640
+ parameters: this.options.callback(form, value),
641
+ onComplete: this.onComplete.bind(this),
642
+ onFailure: this.onFailure.bind(this)
643
+ }, this.options.ajaxOptions)
644
+ );
645
+ // stop the event to avoid a page refresh in Safari
646
+ if (arguments.length > 1) {
647
+ Event.stop(arguments[0]);
648
+ }
649
+ return false;
650
+ },
651
+ onLoading: function() {
652
+ this.saving = true;
653
+ this.removeForm();
654
+ this.leaveHover();
655
+ this.showSaving();
656
+ },
657
+ showSaving: function() {
658
+ this.oldInnerHTML = this.element.innerHTML;
659
+ this.element.innerHTML = this.options.savingText;
660
+ Element.addClassName(this.element, this.options.savingClassName);
661
+ this.element.style.backgroundColor = this.originalBackground;
662
+ Element.show(this.element);
663
+ },
664
+ removeForm: function() {
665
+ if(this.form) {
666
+ if (this.form.parentNode) Element.remove(this.form);
667
+ this.form = null;
668
+ }
669
+ },
670
+ enterHover: function() {
671
+ if (this.saving) return;
672
+ this.element.style.backgroundColor = this.options.highlightcolor;
673
+ if (this.effect) {
674
+ this.effect.cancel();
675
+ }
676
+ Element.addClassName(this.element, this.options.hoverClassName)
677
+ },
678
+ leaveHover: function() {
679
+ if (this.options.backgroundColor) {
680
+ this.element.style.backgroundColor = this.oldBackground;
681
+ }
682
+ Element.removeClassName(this.element, this.options.hoverClassName)
683
+ if (this.saving) return;
684
+ this.effect = new Effect.Highlight(this.element, {
685
+ startcolor: this.options.highlightcolor,
686
+ endcolor: this.options.highlightendcolor,
687
+ restorecolor: this.originalBackground
688
+ });
689
+ },
690
+ leaveEditMode: function() {
691
+ Element.removeClassName(this.element, this.options.savingClassName);
692
+ this.removeForm();
693
+ this.leaveHover();
694
+ this.element.style.backgroundColor = this.originalBackground;
695
+ Element.show(this.element);
696
+ if (this.options.externalControl) {
697
+ Element.show(this.options.externalControl);
698
+ }
699
+ this.editing = false;
700
+ this.saving = false;
701
+ this.oldInnerHTML = null;
702
+ this.onLeaveEditMode();
703
+ },
704
+ onComplete: function(transport) {
705
+ this.leaveEditMode();
706
+ this.options.onComplete.bind(this)(transport, this.element);
707
+ },
708
+ onEnterEditMode: function() {},
709
+ onLeaveEditMode: function() {},
710
+ dispose: function() {
711
+ if (this.oldInnerHTML) {
712
+ this.element.innerHTML = this.oldInnerHTML;
713
+ }
714
+ this.leaveEditMode();
715
+ Event.stopObserving(this.element, 'click', this.onclickListener);
716
+ Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
717
+ Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
718
+ if (this.options.externalControl) {
719
+ Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
720
+ Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
721
+ Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
722
+ }
723
+ }
724
+ };
725
+
726
+ // Delayed observer, like Form.Element.Observer,
727
+ // but waits for delay after last key input
728
+ // Ideal for live-search fields
729
+
730
+ Form.Element.DelayedObserver = Class.create();
731
+ Form.Element.DelayedObserver.prototype = {
732
+ initialize: function(element, delay, callback) {
733
+ this.delay = delay || 0.5;
734
+ this.element = $(element);
735
+ this.callback = callback;
736
+ this.timer = null;
737
+ this.lastValue = $F(this.element);
738
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
739
+ },
740
+ delayedListener: function(event) {
741
+ if(this.lastValue == $F(this.element)) return;
742
+ if(this.timer) clearTimeout(this.timer);
743
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
744
+ this.lastValue = $F(this.element);
745
+ },
746
+ onTimerEvent: function() {
747
+ this.timer = null;
748
+ this.callback(this.element, $F(this.element));
749
+ }
750
+ };