typo 3.99.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (742) hide show
  1. data/.DS_Store +0 -0
  2. data/CHANGELOG +718 -0
  3. data/MAINTAINERS +30 -0
  4. data/MIT-LICENSE +21 -0
  5. data/README +92 -0
  6. data/Rakefile +10 -0
  7. data/app/apis/blogger_service.rb +94 -0
  8. data/app/apis/meta_weblog_service.rb +197 -0
  9. data/app/apis/movable_type_service.rb +150 -0
  10. data/app/apis/typo_web_service.rb +26 -0
  11. data/app/controllers/accounts_controller.rb +52 -0
  12. data/app/controllers/admin/base_controller.rb +21 -0
  13. data/app/controllers/admin/blacklist_controller.rb +50 -0
  14. data/app/controllers/admin/cache_controller.rb +31 -0
  15. data/app/controllers/admin/categories_controller.rb +64 -0
  16. data/app/controllers/admin/comments_controller.rb +57 -0
  17. data/app/controllers/admin/content_controller.rb +147 -0
  18. data/app/controllers/admin/general_controller.rb +42 -0
  19. data/app/controllers/admin/pages_controller.rb +50 -0
  20. data/app/controllers/admin/resources_controller.rb +98 -0
  21. data/app/controllers/admin/sidebar_controller.rb +74 -0
  22. data/app/controllers/admin/textfilters_controller.rb +104 -0
  23. data/app/controllers/admin/themes_controller.rb +20 -0
  24. data/app/controllers/admin/trackbacks_controller.rb +54 -0
  25. data/app/controllers/admin/users_controller.rb +41 -0
  26. data/app/controllers/application.rb +66 -0
  27. data/app/controllers/articles_controller.rb +218 -0
  28. data/app/controllers/backend_controller.rb +11 -0
  29. data/app/controllers/content_controller.rb +98 -0
  30. data/app/controllers/live_controller.rb +10 -0
  31. data/app/controllers/redirect_controller.rb +16 -0
  32. data/app/controllers/sidebar_controller.rb +50 -0
  33. data/app/controllers/textfilter_controller.rb +22 -0
  34. data/app/controllers/theme_controller.rb +52 -0
  35. data/app/controllers/xml_controller.rb +111 -0
  36. data/app/helpers/accounts_helper.rb +2 -0
  37. data/app/helpers/admin/base_helper.rb +124 -0
  38. data/app/helpers/admin/blacklist_helper.rb +2 -0
  39. data/app/helpers/admin/cache_helper.rb +2 -0
  40. data/app/helpers/admin/comments_helper.rb +2 -0
  41. data/app/helpers/admin/content_helper.rb +2 -0
  42. data/app/helpers/admin/general_helper.rb +2 -0
  43. data/app/helpers/admin/pages_helper.rb +2 -0
  44. data/app/helpers/admin/resources_helper.rb +2 -0
  45. data/app/helpers/admin/sidebar_helper.rb +2 -0
  46. data/app/helpers/admin/textfilters_helper.rb +2 -0
  47. data/app/helpers/admin/themes_helper.rb +2 -0
  48. data/app/helpers/admin/trackbacks_helper.rb +2 -0
  49. data/app/helpers/admin/users_helper.rb +2 -0
  50. data/app/helpers/application_helper.rb +92 -0
  51. data/app/helpers/articles_helper.rb +163 -0
  52. data/app/helpers/backend_helper.rb +2 -0
  53. data/app/helpers/content_helper.rb +3 -0
  54. data/app/helpers/mail_helper.rb +6 -0
  55. data/app/helpers/redirect_helper.rb +2 -0
  56. data/app/helpers/sidebar_helper.rb +22 -0
  57. data/app/helpers/sidebars/plugin_helper.rb +2 -0
  58. data/app/helpers/text_filter_plugin_helper.rb +2 -0
  59. data/app/helpers/textfilter_helper.rb +2 -0
  60. data/app/helpers/theme_helper.rb +23 -0
  61. data/app/helpers/xml_helper.rb +21 -0
  62. data/app/models/aggregations/audioscrobbler.rb +54 -0
  63. data/app/models/aggregations/backpack.rb +57 -0
  64. data/app/models/aggregations/delicious.rb +57 -0
  65. data/app/models/aggregations/flickr.rb +89 -0
  66. data/app/models/aggregations/fortythree.rb +62 -0
  67. data/app/models/aggregations/magnolia.rb +81 -0
  68. data/app/models/aggregations/tada.rb +75 -0
  69. data/app/models/aggregations/technorati.rb +51 -0
  70. data/app/models/aggregations/upcoming.rb +74 -0
  71. data/app/models/article.rb +211 -0
  72. data/app/models/blacklist_pattern.rb +22 -0
  73. data/app/models/blog.rb +162 -0
  74. data/app/models/blog_sweeper.rb +23 -0
  75. data/app/models/category.rb +62 -0
  76. data/app/models/comment.rb +76 -0
  77. data/app/models/config_manager.rb +80 -0
  78. data/app/models/content.rb +222 -0
  79. data/app/models/content_observer.rb +5 -0
  80. data/app/models/content_state/base.rb +51 -0
  81. data/app/models/content_state/draft.rb +40 -0
  82. data/app/models/content_state/factory.rb +16 -0
  83. data/app/models/content_state/just_published.rb +47 -0
  84. data/app/models/content_state/new.rb +45 -0
  85. data/app/models/content_state/publication_pending.rb +43 -0
  86. data/app/models/content_state/published.rb +35 -0
  87. data/app/models/email_notifier.rb +9 -0
  88. data/app/models/notification_mailer.rb +34 -0
  89. data/app/models/page.rb +20 -0
  90. data/app/models/page_cache.rb +29 -0
  91. data/app/models/ping.rb +129 -0
  92. data/app/models/redirect.rb +3 -0
  93. data/app/models/resource.rb +76 -0
  94. data/app/models/sidebar.rb +32 -0
  95. data/app/models/simple_cache.rb +37 -0
  96. data/app/models/tag.rb +63 -0
  97. data/app/models/text_filter.rb +111 -0
  98. data/app/models/theme.rb +44 -0
  99. data/app/models/trackback.rb +51 -0
  100. data/app/models/trigger.rb +31 -0
  101. data/app/models/user.rb +113 -0
  102. data/app/models/web_notifier.rb +7 -0
  103. data/app/views/accounts/login.rhtml +24 -0
  104. data/app/views/accounts/logout.rhtml +12 -0
  105. data/app/views/accounts/signup.rhtml +26 -0
  106. data/app/views/accounts/welcome.rhtml +13 -0
  107. data/app/views/admin/base/_recent_comments.rhtml +10 -0
  108. data/app/views/admin/base/_recent_trackbacks.rhtml +10 -0
  109. data/app/views/admin/blacklist/_blacklist_patterns.rhtml +16 -0
  110. data/app/views/admin/blacklist/_form.rhtml +15 -0
  111. data/app/views/admin/blacklist/_quick_post.rhtml +16 -0
  112. data/app/views/admin/blacklist/destroy.rhtml +10 -0
  113. data/app/views/admin/blacklist/edit.rhtml +14 -0
  114. data/app/views/admin/blacklist/list.rhtml +13 -0
  115. data/app/views/admin/cache/list.rhtml +8 -0
  116. data/app/views/admin/categories/_categories.rhtml +14 -0
  117. data/app/views/admin/categories/_form.rhtml +7 -0
  118. data/app/views/admin/categories/_quick_post.rhtml +11 -0
  119. data/app/views/admin/categories/destroy.rhtml +10 -0
  120. data/app/views/admin/categories/edit.rhtml +13 -0
  121. data/app/views/admin/categories/list.rhtml +15 -0
  122. data/app/views/admin/categories/reorder.rhtml +9 -0
  123. data/app/views/admin/categories/show.rhtml +18 -0
  124. data/app/views/admin/comments/_form.rhtml +19 -0
  125. data/app/views/admin/comments/comments.rhtml +4 -0
  126. data/app/views/admin/comments/destroy.rhtml +11 -0
  127. data/app/views/admin/comments/edit.rhtml +18 -0
  128. data/app/views/admin/comments/list.rhtml +31 -0
  129. data/app/views/admin/comments/new.rhtml +15 -0
  130. data/app/views/admin/comments/show.rhtml +10 -0
  131. data/app/views/admin/content/_articles.rhtml +20 -0
  132. data/app/views/admin/content/_attachment.rhtml +17 -0
  133. data/app/views/admin/content/_form.rhtml +51 -0
  134. data/app/views/admin/content/_pages.rhtml +3 -0
  135. data/app/views/admin/content/_quick_post.rhtml +20 -0
  136. data/app/views/admin/content/_show_categories.rhtml +9 -0
  137. data/app/views/admin/content/_show_resources.rhtml +9 -0
  138. data/app/views/admin/content/destroy.rhtml +11 -0
  139. data/app/views/admin/content/edit.rhtml +28 -0
  140. data/app/views/admin/content/list.rhtml +20 -0
  141. data/app/views/admin/content/new.rhtml +27 -0
  142. data/app/views/admin/content/preview.rhtml +3 -0
  143. data/app/views/admin/content/show.rhtml +25 -0
  144. data/app/views/admin/general/index.rhtml +216 -0
  145. data/app/views/admin/general/update_database.rhtml +44 -0
  146. data/app/views/admin/pages/_form.rhtml +19 -0
  147. data/app/views/admin/pages/_pages.rhtml +18 -0
  148. data/app/views/admin/pages/_quick_post.rhtml +21 -0
  149. data/app/views/admin/pages/destroy.rhtml +11 -0
  150. data/app/views/admin/pages/edit.rhtml +28 -0
  151. data/app/views/admin/pages/list.rhtml +13 -0
  152. data/app/views/admin/pages/new.rhtml +24 -0
  153. data/app/views/admin/pages/preview.rhtml +1 -0
  154. data/app/views/admin/pages/show.rhtml +14 -0
  155. data/app/views/admin/resources/_itunes_category_add.rhtml +11 -0
  156. data/app/views/admin/resources/_itunes_category_edit.rhtml +32 -0
  157. data/app/views/admin/resources/_metadata_add.rhtml +22 -0
  158. data/app/views/admin/resources/_metadata_edit.rhtml +22 -0
  159. data/app/views/admin/resources/_mime_edit.rhtml +8 -0
  160. data/app/views/admin/resources/_pages.rhtml +3 -0
  161. data/app/views/admin/resources/_resources.rhtml +40 -0
  162. data/app/views/admin/resources/destroy.rhtml +11 -0
  163. data/app/views/admin/resources/list.rhtml +9 -0
  164. data/app/views/admin/resources/new.rhtml +12 -0
  165. data/app/views/admin/sidebar/_active.rhtml +14 -0
  166. data/app/views/admin/sidebar/_actives.rhtml +9 -0
  167. data/app/views/admin/sidebar/_available.rhtml +5 -0
  168. data/app/views/admin/sidebar/_availables.rhtml +5 -0
  169. data/app/views/admin/sidebar/_publish.rhtml +2 -0
  170. data/app/views/admin/sidebar/_target.rhtml +1 -0
  171. data/app/views/admin/sidebar/index.rhtml +34 -0
  172. data/app/views/admin/sidebar/publish.rjs +6 -0
  173. data/app/views/admin/sidebar/remove.rjs +5 -0
  174. data/app/views/admin/sidebar/set_active.rjs +10 -0
  175. data/app/views/admin/textfilters/_form.rhtml +45 -0
  176. data/app/views/admin/textfilters/_macros.rhtml +16 -0
  177. data/app/views/admin/textfilters/_textfilters.rhtml +20 -0
  178. data/app/views/admin/textfilters/destroy.rhtml +10 -0
  179. data/app/views/admin/textfilters/edit.rhtml +17 -0
  180. data/app/views/admin/textfilters/list.rhtml +15 -0
  181. data/app/views/admin/textfilters/macro_help.rhtml +3 -0
  182. data/app/views/admin/textfilters/new.rhtml +15 -0
  183. data/app/views/admin/textfilters/preview.rhtml +3 -0
  184. data/app/views/admin/textfilters/show.rhtml +34 -0
  185. data/app/views/admin/textfilters/show_help.rhtml +8 -0
  186. data/app/views/admin/themes/index.rhtml +12 -0
  187. data/app/views/admin/trackbacks/_form.rhtml +19 -0
  188. data/app/views/admin/trackbacks/destroy.rhtml +6 -0
  189. data/app/views/admin/trackbacks/edit.rhtml +14 -0
  190. data/app/views/admin/trackbacks/list.rhtml +31 -0
  191. data/app/views/admin/trackbacks/new.rhtml +15 -0
  192. data/app/views/admin/trackbacks/show.rhtml +14 -0
  193. data/app/views/admin/users/_form.rhtml +48 -0
  194. data/app/views/admin/users/_user.rhtml +9 -0
  195. data/app/views/admin/users/destroy.rhtml +14 -0
  196. data/app/views/admin/users/edit.rhtml +14 -0
  197. data/app/views/admin/users/list.rhtml +9 -0
  198. data/app/views/admin/users/new.rhtml +15 -0
  199. data/app/views/admin/users/show.rhtml +18 -0
  200. data/app/views/articles/_article.rhtml +4 -0
  201. data/app/views/articles/_comment.rhtml +7 -0
  202. data/app/views/articles/_comment_box.rhtml +43 -0
  203. data/app/views/articles/_comment_error.rhtml +1 -0
  204. data/app/views/articles/_trackback.rhtml +7 -0
  205. data/app/views/articles/archives.rhtml +22 -0
  206. data/app/views/articles/comment_preview.rhtml +3 -0
  207. data/app/views/articles/error.rhtml +3 -0
  208. data/app/views/articles/groupings.rhtml +14 -0
  209. data/app/views/articles/index.rhtml +11 -0
  210. data/app/views/articles/read.rhtml +70 -0
  211. data/app/views/articles/trackback.rxml +5 -0
  212. data/app/views/articles/view_page.rhtml +3 -0
  213. data/app/views/layouts/accounts.rhtml +22 -0
  214. data/app/views/layouts/administration.rhtml +75 -0
  215. data/app/views/layouts/sidebar.rhtml +40 -0
  216. data/app/views/live/search.rhtml +10 -0
  217. data/app/views/notification_mailer/_mail_footer.rhtml +7 -0
  218. data/app/views/notification_mailer/_mail_header.rhtml +1 -0
  219. data/app/views/notification_mailer/article.rhtml +6 -0
  220. data/app/views/notification_mailer/comment.rhtml +11 -0
  221. data/app/views/notification_mailer/trackback.rhtml +3 -0
  222. data/app/views/settings/done.rhtml +2 -0
  223. data/app/views/settings/install.rhtml +12 -0
  224. data/app/views/shared/_search.rhtml +10 -0
  225. data/app/views/sidebar/_row.rhtml +1 -0
  226. data/app/views/sidebar/_sidebar.rhtml +5 -0
  227. data/app/views/sidebar/display_plugins.rhtml +5 -0
  228. data/app/views/sidebar/show.rhtml +1 -0
  229. data/app/views/theme/static_view_test.rhtml +1 -0
  230. data/app/views/xml/_atom10_item_article.rxml +39 -0
  231. data/app/views/xml/_atom10_item_comment.rxml +13 -0
  232. data/app/views/xml/_atom10_item_trackback.rxml +16 -0
  233. data/app/views/xml/_itunes_item_resource.rxml +30 -0
  234. data/app/views/xml/_rss20_item_article.rxml +35 -0
  235. data/app/views/xml/_rss20_item_comment.rxml +7 -0
  236. data/app/views/xml/_rss20_item_trackback.rxml +7 -0
  237. data/app/views/xml/atom10_feed.rxml +18 -0
  238. data/app/views/xml/itunes_feed.rxml +29 -0
  239. data/app/views/xml/rsd.rxml +19 -0
  240. data/app/views/xml/rss20_feed.rxml +18 -0
  241. data/bin/typo +12 -0
  242. data/cache/META/DATA/ACTION_PARAM/localhost.3000/articles/index/.cache +540 -0
  243. data/cache/META/DATA/ACTION_PARAM/localhost.3000/articles/permalink/day=09&month=09&title=i-need-a-new-keyboard-and-mouse&year=2005.cache +414 -0
  244. data/cache/META/META/ACTION_PARAM/localhost.3000/articles/index/.cache +3 -0
  245. data/cache/META/META/ACTION_PARAM/localhost.3000/articles/permalink/day=09&month=09&title=i-need-a-new-keyboard-and-mouse&year=2005.cache +3 -0
  246. data/components/plugins/sidebars/aimpresence/content.rhtml +4 -0
  247. data/components/plugins/sidebars/aimpresence_controller.rb +10 -0
  248. data/components/plugins/sidebars/amazon/content.rhtml +4 -0
  249. data/components/plugins/sidebars/amazon_controller.rb +17 -0
  250. data/components/plugins/sidebars/archives/content.rhtml +11 -0
  251. data/components/plugins/sidebars/archives_controller.rb +24 -0
  252. data/components/plugins/sidebars/audioscrobbler/content.rhtml +10 -0
  253. data/components/plugins/sidebars/audioscrobbler_controller.rb +10 -0
  254. data/components/plugins/sidebars/backpack/content.rhtml +12 -0
  255. data/components/plugins/sidebars/backpack_controller.rb +17 -0
  256. data/components/plugins/sidebars/category/content.rhtml +10 -0
  257. data/components/plugins/sidebars/category_controller.rb +13 -0
  258. data/components/plugins/sidebars/delicious/content.rhtml +20 -0
  259. data/components/plugins/sidebars/delicious_controller.rb +29 -0
  260. data/components/plugins/sidebars/flickr/content.rhtml +15 -0
  261. data/components/plugins/sidebars/flickr_controller.rb +16 -0
  262. data/components/plugins/sidebars/fortythree/content.rhtml +8 -0
  263. data/components/plugins/sidebars/fortythree_controller.rb +12 -0
  264. data/components/plugins/sidebars/fortythreeplaces/content.rhtml +8 -0
  265. data/components/plugins/sidebars/fortythreeplaces_controller.rb +12 -0
  266. data/components/plugins/sidebars/magnolia/content.rhtml +12 -0
  267. data/components/plugins/sidebars/magnolia_controller.rb +17 -0
  268. data/components/plugins/sidebars/recent_comments/content.rhtml +12 -0
  269. data/components/plugins/sidebars/recent_comments_controller.rb +23 -0
  270. data/components/plugins/sidebars/static/content.rhtml +2 -0
  271. data/components/plugins/sidebars/static_controller.rb +23 -0
  272. data/components/plugins/sidebars/tada/content.rhtml +12 -0
  273. data/components/plugins/sidebars/tada_controller.rb +12 -0
  274. data/components/plugins/sidebars/tag/content.rhtml +8 -0
  275. data/components/plugins/sidebars/tag_controller.rb +18 -0
  276. data/components/plugins/sidebars/technorati/content.rhtml +8 -0
  277. data/components/plugins/sidebars/technorati_controller.rb +12 -0
  278. data/components/plugins/sidebars/upcoming/content.rhtml +8 -0
  279. data/components/plugins/sidebars/upcoming_controller.rb +11 -0
  280. data/components/plugins/sidebars/xbox/content.rhtml +2 -0
  281. data/components/plugins/sidebars/xbox_controller.rb +6 -0
  282. data/components/plugins/sidebars/xml/content.rhtml +12 -0
  283. data/components/plugins/sidebars/xml_controller.rb +11 -0
  284. data/components/plugins/textfilters/amazon_controller.rb +39 -0
  285. data/components/plugins/textfilters/code_controller.rb +59 -0
  286. data/components/plugins/textfilters/flickr_controller.rb +75 -0
  287. data/components/plugins/textfilters/htmlfilter_controller.rb +8 -0
  288. data/components/plugins/textfilters/lightbox_controller.rb +122 -0
  289. data/components/plugins/textfilters/macropost_controller.rb +14 -0
  290. data/components/plugins/textfilters/macropre_controller.rb +14 -0
  291. data/components/plugins/textfilters/markdown_controller.rb +32 -0
  292. data/components/plugins/textfilters/none_controller.rb +8 -0
  293. data/components/plugins/textfilters/smartypants_controller.rb +8 -0
  294. data/components/plugins/textfilters/sparkline_controller.rb +87 -0
  295. data/components/plugins/textfilters/textile_controller.rb +8 -0
  296. data/components/sidebars/README +36 -0
  297. data/config/boot.rb +44 -0
  298. data/config/database.yml +17 -0
  299. data/config/database.yml-pgsql +17 -0
  300. data/config/database.yml.example +18 -0
  301. data/config/environment.rb +145 -0
  302. data/config/environments/development.rb +17 -0
  303. data/config/environments/production.rb +19 -0
  304. data/config/environments/test.rb +26 -0
  305. data/config/iTunes.yml +72 -0
  306. data/config/lighttpd.conf +46 -0
  307. data/config/mail.yml +8 -0
  308. data/config/mail.yml.example +8 -0
  309. data/config/routes.rb +92 -0
  310. data/db/DB +0 -0
  311. data/db/DB.test +0 -0
  312. data/db/converters/README +14 -0
  313. data/db/converters/feed.rb +68 -0
  314. data/db/converters/mt-import.rb +72 -0
  315. data/db/converters/mt3.rb +172 -0
  316. data/db/converters/rss.rb +67 -0
  317. data/db/converters/s9y.rb +182 -0
  318. data/db/converters/textpattern.rb +137 -0
  319. data/db/converters/wordpress.rb +187 -0
  320. data/db/development_structure.sql +691 -0
  321. data/db/migrate/001_initial_schema.rb +127 -0
  322. data/db/migrate/002_add_user_email.rb +17 -0
  323. data/db/migrate/003_add_article_user_id.rb +20 -0
  324. data/db/migrate/004_add_sidebars.rb +31 -0
  325. data/db/migrate/005_add_cache_table.rb +20 -0
  326. data/db/migrate/006_add_pages.rb +17 -0
  327. data/db/migrate/007_add_permalink.rb +36 -0
  328. data/db/migrate/008_add_page_title.rb +9 -0
  329. data/db/migrate/009_add_article_guid.rb +10 -0
  330. data/db/migrate/010_add_tags.rb +19 -0
  331. data/db/migrate/011_add_article_id.rb +17 -0
  332. data/db/migrate/012_enlarge_settings.rb +11 -0
  333. data/db/migrate/013_add_textfilters.rb +33 -0
  334. data/db/migrate/014_move_text_filter_to_text_filter_id.rb +43 -0
  335. data/db/migrate/015_convert_mysql_to_innodb.rb +19 -0
  336. data/db/migrate/016_fix_is_primary_postgres.rb +19 -0
  337. data/db/migrate/017_add_comment_user_id.rb +23 -0
  338. data/db/migrate/018_add_guids.rb +13 -0
  339. data/db/migrate/019_add_whiteboards_to_content.rb +15 -0
  340. data/db/migrate/020_superclass_articles.rb +167 -0
  341. data/db/migrate/021_superclass_comments.rb +94 -0
  342. data/db/migrate/022_superclass_trackbacks.rb +71 -0
  343. data/db/migrate/023_superclass_pages.rb +64 -0
  344. data/db/migrate/024_cleanup_contents.rb +54 -0
  345. data/db/migrate/025_add_itunes_metadata.rb +29 -0
  346. data/db/migrate/026_add_redirect_table.rb +13 -0
  347. data/db/migrate/027_set_comment_published_flag.rb +19 -0
  348. data/db/migrate/028_rename_redirect_to.rb +12 -0
  349. data/db/migrate/029_add_user_notification.rb +29 -0
  350. data/db/migrate/030_index_sessions.rb +9 -0
  351. data/db/migrate/031_add_notifications_table.rb +14 -0
  352. data/db/migrate/032_add_jabber_notification.rb +11 -0
  353. data/db/migrate/033_add_count_caching.rb +34 -0
  354. data/db/migrate/034_boolify_published.rb +36 -0
  355. data/db/migrate/035_boolify_content_allow_foo.rb +36 -0
  356. data/db/migrate/036_add_tag_display_name.rb +53 -0
  357. data/db/migrate/037_enlarge_ip_field.rb +9 -0
  358. data/db/migrate/038_add_blog_object.rb +52 -0
  359. data/db/migrate/039_serialize_blog_attributes.rb +160 -0
  360. data/db/migrate/040_attach_content_to_blog.rb +28 -0
  361. data/db/migrate/041_fixup_default_sidebars.rb +18 -0
  362. data/db/migrate/042_remove_sidebar_staged_config.rb +22 -0
  363. data/db/migrate/043_create_triggers.rb +14 -0
  364. data/db/migrate/044_add_published_at_to_content.rb +18 -0
  365. data/db/migrate/045_fix_contents_published_default.rb +9 -0
  366. data/db/migrate/046_fixup_forthcoming_publications.rb +40 -0
  367. data/db/schema.mysql-v3.sql +218 -0
  368. data/db/schema.mysql.sql +218 -0
  369. data/db/schema.postgresql.sql +218 -0
  370. data/db/schema.rb +169 -0
  371. data/db/schema.sqlite.sql +218 -0
  372. data/db/schema.sqlserver.sql +232 -0
  373. data/db/schema_version +1 -0
  374. data/db/scripts/fix_permalinks.rb +5 -0
  375. data/db/updates/update.168.to.200.mysql.sql +33 -0
  376. data/db/updates/update.168.to.200.psql.sql +30 -0
  377. data/doc/Installer.txt +55 -0
  378. data/doc/README_FOR_APP +2 -0
  379. data/installer/rails-installer.rb +488 -0
  380. data/installer/rails_installer_defaults.yml +4 -0
  381. data/installer/typo-installer.rb +35 -0
  382. data/lib/backpack_api.rb +202 -0
  383. data/lib/bare_migration.rb +142 -0
  384. data/lib/email_notify.rb +32 -0
  385. data/lib/format.rb +19 -0
  386. data/lib/generators/sidebar/sidebar_generator.rb +13 -0
  387. data/lib/generators/sidebar/templates/components/plugins/sidebars/controller_template.rb +20 -0
  388. data/lib/generators/sidebar/templates/components/plugins/sidebars/views/content_template.rhtml +4 -0
  389. data/lib/jabber_notify.rb +32 -0
  390. data/lib/login_system.rb +85 -0
  391. data/lib/migrator.rb +28 -0
  392. data/lib/rails_patch/active_record.rb +36 -0
  393. data/lib/rails_patch/components.rb +14 -0
  394. data/lib/sidebars/component_plugin.rb +5 -0
  395. data/lib/sidebars/plugin.rb +241 -0
  396. data/lib/spam_protection.rb +126 -0
  397. data/lib/tasks/release.rake +171 -0
  398. data/lib/tasks/schemas.rake +6 -0
  399. data/lib/tasks/sweep_cache.rake +6 -0
  400. data/lib/text_filter_plugin.rb +97 -0
  401. data/lib/transforms.rb +26 -0
  402. data/lib/typo_guid.rb +10 -0
  403. data/lib/typo_plugins.rb +19 -0
  404. data/lib/typo_version.rb +1 -0
  405. data/lib/xmlrpc_fix.rb +13 -0
  406. data/public/.htaccess +34 -0
  407. data/public/404.html +8 -0
  408. data/public/500.html +8 -0
  409. data/public/dispatch.cgi +10 -0
  410. data/public/dispatch.fcgi +24 -0
  411. data/public/dispatch.rb +10 -0
  412. data/public/favicon.ico +0 -0
  413. data/public/images/admin/dot-vertical.gif +0 -0
  414. data/public/images/admin/h-tile.gif +0 -0
  415. data/public/images/admin/hdr-tile.gif +0 -0
  416. data/public/images/admin/toggle.gif +0 -0
  417. data/public/images/bg.jpg +0 -0
  418. data/public/images/bgcolor.jpg +0 -0
  419. data/public/images/body_bg.png +0 -0
  420. data/public/images/branding_bg.png +0 -0
  421. data/public/images/branding_logo.png +0 -0
  422. data/public/images/checked.gif +0 -0
  423. data/public/images/content_bg.png +0 -0
  424. data/public/images/delete.png +0 -0
  425. data/public/images/footer.jpg +0 -0
  426. data/public/images/footer.png +0 -0
  427. data/public/images/global_bg.png +0 -0
  428. data/public/images/global_logo.png +0 -0
  429. data/public/images/go.png +0 -0
  430. data/public/images/header.jpg +0 -0
  431. data/public/images/loading.gif +0 -0
  432. data/public/images/not-checked copy.gif +0 -0
  433. data/public/images/not-checked.gif +0 -0
  434. data/public/images/overlay.png +0 -0
  435. data/public/images/powered.gif +0 -0
  436. data/public/images/section_bg.png +0 -0
  437. data/public/images/sections_bg.png +0 -0
  438. data/public/images/sections_hilite.png +0 -0
  439. data/public/images/sections_hover.png +0 -0
  440. data/public/images/spinner-blue.gif +0 -0
  441. data/public/images/spinner.gif +0 -0
  442. data/public/images/spot-header.gif +0 -0
  443. data/public/images/spot-title.gif +0 -0
  444. data/public/images/x-ed.gif +0 -0
  445. data/public/javascripts/application.js +2 -0
  446. data/public/javascripts/controls.js +815 -0
  447. data/public/javascripts/cookies.js +60 -0
  448. data/public/javascripts/dragdrop.js +724 -0
  449. data/public/javascripts/effects.js +953 -0
  450. data/public/javascripts/lightbox.js +348 -0
  451. data/public/javascripts/prototype.js +1985 -0
  452. data/public/javascripts/scriptaculous.js +47 -0
  453. data/public/javascripts/slider.js +258 -0
  454. data/public/javascripts/typo.js +85 -0
  455. data/public/robots.txt +2 -0
  456. data/public/stylesheets/administration.css +585 -0
  457. data/public/stylesheets/lightbox.css +22 -0
  458. data/public/stylesheets/rss.css +54 -0
  459. data/public/stylesheets/user-styles.css +0 -0
  460. data/script/about +3 -0
  461. data/script/benchmarker +19 -0
  462. data/script/breakpointer +3 -0
  463. data/script/console +3 -0
  464. data/script/destroy +3 -0
  465. data/script/generate +3 -0
  466. data/script/lighttpd +2 -0
  467. data/script/logreport +79 -0
  468. data/script/migrate +31 -0
  469. data/script/performance/benchmarker +3 -0
  470. data/script/performance/profiler +3 -0
  471. data/script/plugin +3 -0
  472. data/script/process/reaper +3 -0
  473. data/script/process/spawner +3 -0
  474. data/script/process/spinner +3 -0
  475. data/script/profiler +34 -0
  476. data/script/runner +3 -0
  477. data/script/server +3 -0
  478. data/script/spacecheck +40 -0
  479. data/test/fixtures/articles_categories.yml +31 -0
  480. data/test/fixtures/articles_tags.yml +19 -0
  481. data/test/fixtures/blacklist_patterns.yml +10 -0
  482. data/test/fixtures/blogs.yml +74 -0
  483. data/test/fixtures/categories.yml +35 -0
  484. data/test/fixtures/contents.yml +297 -0
  485. data/test/fixtures/notification_mailer/article +3 -0
  486. data/test/fixtures/notification_mailer/comment +3 -0
  487. data/test/fixtures/notification_mailer/trackback +3 -0
  488. data/test/fixtures/notifications.yml +0 -0
  489. data/test/fixtures/page_caches.yml +7 -0
  490. data/test/fixtures/redirects.yml +13 -0
  491. data/test/fixtures/resources.yml +24 -0
  492. data/test/fixtures/sidebars.yml +5 -0
  493. data/test/fixtures/tags.yml +13 -0
  494. data/test/fixtures/text_filters.yml +42 -0
  495. data/test/fixtures/triggers.yml +1 -0
  496. data/test/fixtures/users.yml +56 -0
  497. data/test/functional/accounts_controller_test.rb +82 -0
  498. data/test/functional/admin/article_preview_test.rb +67 -0
  499. data/test/functional/admin/blacklist_controller_test.rb +63 -0
  500. data/test/functional/admin/categories_controller_test.rb +112 -0
  501. data/test/functional/admin/comments_controller_test.rb +78 -0
  502. data/test/functional/admin/content_controller_test.rb +234 -0
  503. data/test/functional/admin/general_controller_test.rb +26 -0
  504. data/test/functional/admin/pages_controller_test.rb +103 -0
  505. data/test/functional/admin/resources_controller_test.rb +49 -0
  506. data/test/functional/admin/textfilters_controller_test.rb +29 -0
  507. data/test/functional/admin/themes_controller_test.rb +34 -0
  508. data/test/functional/admin/trackbacks_controller_test.rb +76 -0
  509. data/test/functional/admin/users_controller_test.rb +72 -0
  510. data/test/functional/articles_controller_test.rb +525 -0
  511. data/test/functional/backend_controller_test.rb +273 -0
  512. data/test/functional/redirect_controller_test.rb +40 -0
  513. data/test/functional/textfilter_controller_test.rb +275 -0
  514. data/test/functional/theme_controller_test.rb +45 -0
  515. data/test/functional/xml_controller_test.rb +353 -0
  516. data/test/mocks/test/dns_mock.rb +13 -0
  517. data/test/mocks/test/flickr_mock.rb +30 -0
  518. data/test/mocks/test/http_mock.rb +38 -0
  519. data/test/mocks/test/theme_mock.rb +5 -0
  520. data/test/mocks/test/xmlrpc_mock.rb +27 -0
  521. data/test/mocks/themes/123-numbers-in-path/about.markdown +0 -0
  522. data/test/mocks/themes/CamelCaseDirectory/about.markdown +0 -0
  523. data/test/mocks/themes/azure/about.markdown +5 -0
  524. data/test/mocks/themes/azure/layouts/default.rhtml +50 -0
  525. data/test/mocks/themes/azure/preview.png +0 -0
  526. data/test/mocks/themes/azure/views/theme/static_view_test.rhtml +1 -0
  527. data/test/mocks/themes/i-have-special-chars/about.markdown +0 -0
  528. data/test/test_helper.rb +95 -0
  529. data/test/unit/article_test.rb +245 -0
  530. data/test/unit/assumptions_tests.rb +54 -0
  531. data/test/unit/audioscrobbler_test.rb +132 -0
  532. data/test/unit/blacklist_pattern_test.rb +14 -0
  533. data/test/unit/blog_test.rb +83 -0
  534. data/test/unit/category_test.rb +37 -0
  535. data/test/unit/comment_test.rb +117 -0
  536. data/test/unit/configuration_test.rb +21 -0
  537. data/test/unit/content_state/factory_test.rb +84 -0
  538. data/test/unit/delicious_test.rb +96 -0
  539. data/test/unit/flickr_test.rb +134 -0
  540. data/test/unit/fortythree_test.rb +66 -0
  541. data/test/unit/magnolia_test.rb +87 -0
  542. data/test/unit/metafragment_test.rb +63 -0
  543. data/test/unit/notification_mailer_test.rb +55 -0
  544. data/test/unit/observer_test.rb +40 -0
  545. data/test/unit/page_cache_test.rb +34 -0
  546. data/test/unit/page_test.rb +25 -0
  547. data/test/unit/ping_test.rb +111 -0
  548. data/test/unit/redirect_test.rb +19 -0
  549. data/test/unit/resource_test.rb +71 -0
  550. data/test/unit/sidebar_test.rb +14 -0
  551. data/test/unit/tag_test.rb +66 -0
  552. data/test/unit/text_filter_test.rb +126 -0
  553. data/test/unit/theme_test.rb +44 -0
  554. data/test/unit/trackback_test.rb +45 -0
  555. data/test/unit/trigger_test.rb +40 -0
  556. data/test/unit/user_test.rb +111 -0
  557. data/themes/azure/about.markdown +5 -0
  558. data/themes/azure/images/bg-tile.gif +0 -0
  559. data/themes/azure/images/bracket.gif +0 -0
  560. data/themes/azure/images/bullet.gif +0 -0
  561. data/themes/azure/images/hdr.gif +0 -0
  562. data/themes/azure/images/q-close.gif +0 -0
  563. data/themes/azure/images/q-open.gif +0 -0
  564. data/themes/azure/images/sbar-tile.gif +0 -0
  565. data/themes/azure/images/spinner.gif +0 -0
  566. data/themes/azure/images/xlink.gif +0 -0
  567. data/themes/azure/layouts/default.rhtml +50 -0
  568. data/themes/azure/preview.png +0 -0
  569. data/themes/azure/stylesheets/azure.css +436 -0
  570. data/themes/azure/stylesheets/print.css +30 -0
  571. data/themes/azure/views/theme/static_view_test.rhtml +1 -0
  572. data/vendor/akismet/Akismet.rb +140 -0
  573. data/vendor/bluecloth/CHANGES +366 -0
  574. data/vendor/bluecloth/LICENSE +340 -0
  575. data/vendor/bluecloth/README +99 -0
  576. data/vendor/bluecloth/bin/bluecloth +83 -0
  577. data/vendor/bluecloth/install.rb +150 -0
  578. data/vendor/bluecloth/lib/bluecloth.rb +1144 -0
  579. data/vendor/bluecloth/test.rb +117 -0
  580. data/vendor/bluecloth/tests/00_Class.tests.rb +71 -0
  581. data/vendor/bluecloth/tests/05_Markdown.tests.rb +1527 -0
  582. data/vendor/bluecloth/tests/10_Bug.tests.rb +57 -0
  583. data/vendor/bluecloth/tests/15_Contrib.tests.rb +132 -0
  584. data/vendor/bluecloth/tests/bctestcase.rb +274 -0
  585. data/vendor/bluecloth/tests/data/antsugar.txt +34 -0
  586. data/vendor/bluecloth/tests/data/ml-announce.txt +17 -0
  587. data/vendor/bluecloth/tests/data/re-overflow.txt +67 -0
  588. data/vendor/bluecloth/tests/data/re-overflow2.txt +281 -0
  589. data/vendor/bluecloth/utils.rb +553 -0
  590. data/vendor/flickr/Rakefile +36 -0
  591. data/vendor/flickr/flickr.rb +490 -0
  592. data/vendor/flickr/index.html +129 -0
  593. data/vendor/flickr/test_flickr.rb +173 -0
  594. data/vendor/jabber4r/CHANGES +8 -0
  595. data/vendor/jabber4r/LICENSE.txt +12 -0
  596. data/vendor/jabber4r/README +180 -0
  597. data/vendor/jabber4r/Rakefile.rb +143 -0
  598. data/vendor/jabber4r/lib/jabber4r/jabber4r.rb +22 -0
  599. data/vendor/jabber4r/lib/jabber4r/jid.rb +93 -0
  600. data/vendor/jabber4r/lib/jabber4r/protocol.rb +1384 -0
  601. data/vendor/jabber4r/lib/jabber4r/rexml_1.8_patch.rb +16 -0
  602. data/vendor/jabber4r/lib/jabber4r/roster.rb +322 -0
  603. data/vendor/jabber4r/lib/jabber4r/session.rb +615 -0
  604. data/vendor/jabber4r/lib/jabber4r/vcard.rb +42 -0
  605. data/vendor/plugins/expiring_action_cache/init.rb +2 -0
  606. data/vendor/plugins/expiring_action_cache/lib/actionparamcache.rb +113 -0
  607. data/vendor/plugins/expiring_action_cache/lib/metafragment.rb +46 -0
  608. data/vendor/plugins/upload_progress/CHANGELOG +3 -0
  609. data/vendor/plugins/upload_progress/MIT-LICENSE +20 -0
  610. data/vendor/plugins/upload_progress/README +45 -0
  611. data/vendor/plugins/upload_progress/Rakefile +23 -0
  612. data/vendor/plugins/upload_progress/init.rb +7 -0
  613. data/vendor/plugins/upload_progress/lib/multipart_progress.rb +169 -0
  614. data/vendor/plugins/upload_progress/lib/progress.rb +145 -0
  615. data/vendor/plugins/upload_progress/lib/upload_progress.rb +303 -0
  616. data/vendor/plugins/upload_progress/lib/upload_progress_helper.rb +424 -0
  617. data/vendor/plugins/upload_progress/public/stylesheets/upload_progress.css +21 -0
  618. data/vendor/plugins/upload_progress/test/multipart_progress_testx.rb +364 -0
  619. data/vendor/plugins/upload_progress/test/upload_progress_helper_testx.rb +134 -0
  620. data/vendor/plugins/upload_progress/test/upload_progress_testx.rb +88 -0
  621. data/vendor/redcloth/RedCloth.gemspec +52 -0
  622. data/vendor/redcloth/bin/redcloth +3 -0
  623. data/vendor/redcloth/doc/CHANGELOG +160 -0
  624. data/vendor/redcloth/doc/COPYING +25 -0
  625. data/vendor/redcloth/doc/README +106 -0
  626. data/vendor/redcloth/doc/REFERENCE +216 -0
  627. data/vendor/redcloth/doc/make.rb +359 -0
  628. data/vendor/redcloth/install.rb +1032 -0
  629. data/vendor/redcloth/lib/redcloth.rb +1130 -0
  630. data/vendor/redcloth/run-tests.rb +28 -0
  631. data/vendor/redcloth/setup.rb +1376 -0
  632. data/vendor/redcloth/tests/code.yml +105 -0
  633. data/vendor/redcloth/tests/hard_breaks.yml +26 -0
  634. data/vendor/redcloth/tests/images.yml +171 -0
  635. data/vendor/redcloth/tests/instiki.yml +39 -0
  636. data/vendor/redcloth/tests/links.yml +155 -0
  637. data/vendor/redcloth/tests/lists.yml +77 -0
  638. data/vendor/redcloth/tests/markdown.yml +218 -0
  639. data/vendor/redcloth/tests/poignant.yml +64 -0
  640. data/vendor/redcloth/tests/table.yml +198 -0
  641. data/vendor/redcloth/tests/textism.yml +406 -0
  642. data/vendor/ruby-mp3info/lib/mp3info.rb +720 -0
  643. data/vendor/rubypants/README +114 -0
  644. data/vendor/rubypants/Rakefile +55 -0
  645. data/vendor/rubypants/html/classes/RubyPants.html +924 -0
  646. data/vendor/rubypants/html/created.rid +1 -0
  647. data/vendor/rubypants/html/files/README.html +248 -0
  648. data/vendor/rubypants/html/files/rubypants_rb.html +125 -0
  649. data/vendor/rubypants/html/fr_class_index.html +27 -0
  650. data/vendor/rubypants/html/fr_file_index.html +28 -0
  651. data/vendor/rubypants/html/fr_method_index.html +38 -0
  652. data/vendor/rubypants/html/index.html +24 -0
  653. data/vendor/rubypants/html/rdoc-style.css +172 -0
  654. data/vendor/rubypants/install.rb +9 -0
  655. data/vendor/rubypants/rubypants.rb +490 -0
  656. data/vendor/rubypants/test_rubypants.rb +162 -0
  657. data/vendor/sparklines/README.txt +47 -0
  658. data/vendor/sparklines/USAGE +14 -0
  659. data/vendor/sparklines/lib/sparklines.rb +435 -0
  660. data/vendor/sparklines/samples/sparklinestest.rb +26 -0
  661. data/vendor/sparklines/sparklines_generator.rb +9 -0
  662. data/vendor/sparklines/templates/sparklines_controller.rb +30 -0
  663. data/vendor/sparklines/templates/sparklines_helper.rb +30 -0
  664. data/vendor/syntax/LICENSE +27 -0
  665. data/vendor/syntax/NEWS +16 -0
  666. data/vendor/syntax/README +38 -0
  667. data/vendor/syntax/Rakefile +194 -0
  668. data/vendor/syntax/api/classes/Syntax.html +173 -0
  669. data/vendor/syntax/api/classes/Syntax/Convertors.html +91 -0
  670. data/vendor/syntax/api/classes/Syntax/Convertors/Abstract.html +159 -0
  671. data/vendor/syntax/api/classes/Syntax/Convertors/HTML.html +149 -0
  672. data/vendor/syntax/api/classes/Syntax/Default.html +123 -0
  673. data/vendor/syntax/api/classes/Syntax/Ruby.html +319 -0
  674. data/vendor/syntax/api/classes/Syntax/Token.html +151 -0
  675. data/vendor/syntax/api/classes/Syntax/Tokenizer.html +324 -0
  676. data/vendor/syntax/api/classes/Syntax/Version.html +109 -0
  677. data/vendor/syntax/api/classes/Syntax/XML.html +205 -0
  678. data/vendor/syntax/api/classes/Syntax/YAML.html +189 -0
  679. data/vendor/syntax/api/created.rid +1 -0
  680. data/vendor/syntax/api/files/README.html +155 -0
  681. data/vendor/syntax/api/files/lib/syntax/common_rb.html +96 -0
  682. data/vendor/syntax/api/files/lib/syntax/convertors/abstract_rb.html +96 -0
  683. data/vendor/syntax/api/files/lib/syntax/convertors/html_rb.html +96 -0
  684. data/vendor/syntax/api/files/lib/syntax/lang/ruby_rb.html +96 -0
  685. data/vendor/syntax/api/files/lib/syntax/lang/xml_rb.html +96 -0
  686. data/vendor/syntax/api/files/lib/syntax/lang/yaml_rb.html +96 -0
  687. data/vendor/syntax/api/files/lib/syntax/version_rb.html +92 -0
  688. data/vendor/syntax/api/files/lib/syntax_rb.html +96 -0
  689. data/vendor/syntax/api/fr_class_index.html +56 -0
  690. data/vendor/syntax/api/fr_file_index.html +54 -0
  691. data/vendor/syntax/api/fr_method_index.html +65 -0
  692. data/vendor/syntax/api/index.html +26 -0
  693. data/vendor/syntax/api/rdoc-style.css +175 -0
  694. data/vendor/syntax/doc/manual-html/chapter-1.html +198 -0
  695. data/vendor/syntax/doc/manual-html/chapter-2.html +262 -0
  696. data/vendor/syntax/doc/manual-html/chapter-3.html +266 -0
  697. data/vendor/syntax/doc/manual-html/chapter-4.html +267 -0
  698. data/vendor/syntax/doc/manual-html/index.html +152 -0
  699. data/vendor/syntax/doc/manual-html/stylesheets/manual.css +270 -0
  700. data/vendor/syntax/doc/manual-html/stylesheets/ruby.css +17 -0
  701. data/vendor/syntax/doc/manual/chapter.erb +38 -0
  702. data/vendor/syntax/doc/manual/example.erb +18 -0
  703. data/vendor/syntax/doc/manual/index.erb +29 -0
  704. data/vendor/syntax/doc/manual/manual.rb +311 -0
  705. data/vendor/syntax/doc/manual/manual.yml +43 -0
  706. data/vendor/syntax/doc/manual/page.erb +87 -0
  707. data/vendor/syntax/doc/manual/parts/0000.txt +5 -0
  708. data/vendor/syntax/doc/manual/parts/0001.txt +16 -0
  709. data/vendor/syntax/doc/manual/parts/0002.txt +24 -0
  710. data/vendor/syntax/doc/manual/parts/0003.txt +6 -0
  711. data/vendor/syntax/doc/manual/parts/0004.txt +32 -0
  712. data/vendor/syntax/doc/manual/parts/0005.txt +18 -0
  713. data/vendor/syntax/doc/manual/parts/0006.txt +62 -0
  714. data/vendor/syntax/doc/manual/parts/0007.txt +3 -0
  715. data/vendor/syntax/doc/manual/parts/0008.txt +5 -0
  716. data/vendor/syntax/doc/manual/parts/0009.txt +29 -0
  717. data/vendor/syntax/doc/manual/parts/0010.txt +21 -0
  718. data/vendor/syntax/doc/manual/stylesheets/manual.css +270 -0
  719. data/vendor/syntax/doc/manual/stylesheets/ruby.css +17 -0
  720. data/vendor/syntax/doc/manual/tutorial.erb +30 -0
  721. data/vendor/syntax/lib/syntax.rb +38 -0
  722. data/vendor/syntax/lib/syntax/common.rb +163 -0
  723. data/vendor/syntax/lib/syntax/convertors/abstract.rb +27 -0
  724. data/vendor/syntax/lib/syntax/convertors/html.rb +51 -0
  725. data/vendor/syntax/lib/syntax/lang/ruby.rb +317 -0
  726. data/vendor/syntax/lib/syntax/lang/xml.rb +108 -0
  727. data/vendor/syntax/lib/syntax/lang/yaml.rb +105 -0
  728. data/vendor/syntax/lib/syntax/version.rb +9 -0
  729. data/vendor/syntax/setup.rb +1331 -0
  730. data/vendor/syntax/syntax.gemspec +24 -0
  731. data/vendor/syntax/test/ALL-TESTS.rb +5 -0
  732. data/vendor/syntax/test/syntax/tc_ruby.rb +871 -0
  733. data/vendor/syntax/test/syntax/tc_xml.rb +202 -0
  734. data/vendor/syntax/test/syntax/tc_yaml.rb +228 -0
  735. data/vendor/syntax/test/syntax/tokenizer_testcase.rb +40 -0
  736. data/vendor/syntax/test/tc_syntax.rb +22 -0
  737. data/vendor/uuidtools/CHANGELOG +4 -0
  738. data/vendor/uuidtools/README +13 -0
  739. data/vendor/uuidtools/install.rb +30 -0
  740. data/vendor/uuidtools/lib/uuidtools.rb +555 -0
  741. data/vendor/uuidtools/rakefile +112 -0
  742. metadata +1022 -0
@@ -0,0 +1,145 @@
1
+ module UploadProgress #:nodoc:
2
+ # Upload Progress abstracts the progress of an upload. It's used by the
3
+ # multipart progress IO that keeps track of the upload progress and creating
4
+ # the application depends on. It contians methods to update the progress
5
+ # during an upload and read the statistics such as +received_bytes+,
6
+ # +total_bytes+, +completed_percent+, +bitrate+, and
7
+ # +remaining_seconds+
8
+ #
9
+ # You can get the current +Progress+ object by calling +upload_progress+ instance
10
+ # method in your controller or view.
11
+ #
12
+ class Progress
13
+ unless const_defined? :MIN_SAMPLE_TIME
14
+ # Number of seconds between bitrate samples. Updates that occur more
15
+ # frequently than +MIN_SAMPLE_TIME+ will not be queued until this
16
+ # time passes. This behavior gives a good balance of accuracy and load
17
+ # for both fast and slow transfers.
18
+ MIN_SAMPLE_TIME = 0.150
19
+
20
+ # Number of seconds between updates before giving up to try and calculate
21
+ # bitrate anymore
22
+ MIN_STALL_TIME = 10.0
23
+
24
+ # Number of samples used to calculate bitrate
25
+ MAX_SAMPLES = 20
26
+ end
27
+
28
+ # Number bytes received from the multipart post
29
+ attr_reader :received_bytes
30
+
31
+ # Total number of bytes expected from the mutlipart post
32
+ attr_reader :total_bytes
33
+
34
+ # The last time the upload history was updated
35
+ attr_reader :last_update_time
36
+
37
+ # A message you can set from your controller or view to be rendered in the
38
+ # +upload_status_text+ helper method. If you set a messagein a controller
39
+ # then call <code>session.update</code> to make that message available to
40
+ # your +upload_status+ action.
41
+ attr_accessor :message
42
+
43
+ # Create a new Progress object passing the expected number of bytes to receive
44
+ def initialize(total)
45
+ @total_bytes = total
46
+ reset!
47
+ end
48
+
49
+ # Resets the received_bytes, last_update_time, message and bitrate, but
50
+ # but maintains the total expected bytes
51
+ def reset!
52
+ @received_bytes, @last_update_time, @stalled, @message = 0, 0, false, ''
53
+ reset_history
54
+ end
55
+
56
+ # Number of bytes left for this upload
57
+ def remaining_bytes
58
+ @total_bytes - @received_bytes
59
+ end
60
+
61
+ # Completed percent in integer form from 0..100
62
+ def completed_percent
63
+ (@received_bytes * 100 / @total_bytes).to_i rescue 0
64
+ end
65
+
66
+ # Updates this UploadProgress object with the number of bytes received
67
+ # since last update time and the absolute number of seconds since the
68
+ # beginning of the upload.
69
+ #
70
+ # This method is used by the +MultipartProgress+ module and should
71
+ # not be called directly.
72
+ def update!(bytes, elapsed_seconds)#:nodoc:
73
+ if @received_bytes + bytes > @total_bytes
74
+ #warn "Progress#update received bytes exceeds expected bytes"
75
+ bytes = @total_bytes - @received_bytes
76
+ end
77
+
78
+ @received_bytes += bytes
79
+
80
+ # Age is the duration of time since the last update to the history
81
+ age = elapsed_seconds - @last_update_time
82
+
83
+ # Record the bytes received in the first element of the history
84
+ # in case the sample rate is exceeded and we shouldn't record at this
85
+ # time
86
+ @history.first[0] += bytes
87
+ @history.first[1] += age
88
+
89
+ history_age = @history.first[1]
90
+
91
+ @history.pop while @history.size > MAX_SAMPLES
92
+ @history.unshift([0,0]) if history_age > MIN_SAMPLE_TIME
93
+
94
+ if history_age > MIN_STALL_TIME
95
+ @stalled = true
96
+ reset_history
97
+ else
98
+ @stalled = false
99
+ end
100
+
101
+ @last_update_time = elapsed_seconds
102
+
103
+ self
104
+ end
105
+
106
+ # Calculates the bitrate in bytes/second. If the transfer is stalled or
107
+ # just started, the bitrate will be 0
108
+ def bitrate
109
+ history_bytes, history_time = @history.transpose.map { |vals| vals.inject { |sum, v| sum + v } }
110
+ history_bytes / history_time rescue 0
111
+ end
112
+
113
+ # Number of seconds elapsed since the start of the upload
114
+ def elapsed_seconds
115
+ @last_update_time
116
+ end
117
+
118
+ # Calculate the seconds remaining based on the current bitrate. Returns
119
+ # O seconds if stalled or if no bytes have been received
120
+ def remaining_seconds
121
+ remaining_bytes / bitrate rescue 0
122
+ end
123
+
124
+ # Returns true if there are bytes pending otherwise returns false
125
+ def finished?
126
+ remaining_bytes <= 0
127
+ end
128
+
129
+ # Returns true if some bytes have been received
130
+ def started?
131
+ @received_bytes > 0
132
+ end
133
+
134
+ # Returns true if there has been a delay in receiving bytes. The delay
135
+ # is set by the constant MIN_STALL_TIME
136
+ def stalled?
137
+ @stalled
138
+ end
139
+
140
+ private
141
+ def reset_history
142
+ @history = [[0,0]]
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,303 @@
1
+ module UploadProgress
2
+ def self.append_features(base) #:nodoc:
3
+ super
4
+ base.extend(ClassMethods)
5
+ base.helper_method :upload_progress, :next_upload_id, :last_upload_id, :current_upload_id
6
+ end
7
+
8
+ # == Action Pack Upload Progress for multipart uploads
9
+ #
10
+ # The UploadProgress module aids in the process of viewing an Ajax driven
11
+ # upload status when working with multipart forms. It offers a macro that
12
+ # will prepare an action for handling the cleanup of the Ajax updating including
13
+ # passing the redirect URL and custom parameters to the Javascript finish handler.
14
+ #
15
+ # UploadProgress is available for all multipart uploads when the +upload_status_for+
16
+ # macro is called in one of your controllers.
17
+ #
18
+ # The progress is stored as an UploadProgress::Progress object in the session and
19
+ # is accessible in the controller and view with the +upload_progress+ method.
20
+ #
21
+ # For help rendering the UploadProgress enabled form and supported elements, see
22
+ # ActionView::Helpers::UploadProgressHelper.
23
+ #
24
+ # === Automatic updating on upload actions
25
+ #
26
+ # class DocumentController < ApplicationController
27
+ # upload_status_for :create
28
+ #
29
+ # def create
30
+ # # ... Your document creation action
31
+ # end
32
+ # end
33
+ #
34
+ # The +upload_status_for+ macro will override the rendering of the action passed
35
+ # if +upload_id+ is found in the query string. This allows for default
36
+ # behavior if Javascript is disabled. If you are tracking the upload progress
37
+ # then +create+ will now return the cleanup scripts that will terminate the polling
38
+ # of the upload status.
39
+ #
40
+ # === Customized status rendering
41
+ #
42
+ # class DocumentController < ApplicationController
43
+ # upload_status_for :create, :status => :custom_status
44
+ #
45
+ # def create
46
+ # # ... Your document creation action
47
+ # end
48
+ #
49
+ # def custom_status
50
+ # # ... Override this action to return content to be replaced in
51
+ # # the status container
52
+ # render :inline => "<%= upload_progress.completed_percent rescue 0 %> % complete", :layout => false
53
+ # end
54
+ #
55
+ # The default status action is +upload_status+. The results of this action
56
+ # are added used to replace the contents of the HTML elements defined in
57
+ # +upload_status_tag+. Within +upload_status+, you can load the Progress
58
+ # object from the session with the +upload_progress+ method and display your own
59
+ # results.
60
+ #
61
+ # Completion of the upload status updating occurs automatically with an +after_filter+ call to
62
+ # +finish_upload_status+. Because the upload must be posted into a hidden IFRAME to enable
63
+ # Ajax updates during the upload, +finish_upload_status+ overwrites the results of any previous
64
+ # +render+ or +redirect_to+ so it can render the necessary Javascript that will properly terminate
65
+ # the status updating loop, trigger the completion callback or redirect to the appropriate URL.
66
+ #
67
+ # ==== Basic Example (View):
68
+ #
69
+ # <%= form_tag_with_upload_progress({:action => 'create'}, {:finish => 'alert("Document Uploaded")'}) %>
70
+ # <%= upload_status_tag %>
71
+ # <%= file_field 'document', 'file' %>
72
+ # <%= end_form_tag %>
73
+ #
74
+ # ==== Basic Example (Controller):
75
+ #
76
+ # class DocumentController < ApplicationController
77
+ # upload_status_for :create
78
+ #
79
+ # def create
80
+ # @document = Document.create(params[:document])
81
+ # end
82
+ # end
83
+ #
84
+ # ==== Extended Example (View):
85
+ #
86
+ # <%= form_tag_with_upload_progress({:action => 'create'}, {}, {:action => :custom_status}) %>
87
+ # <%= upload_status_tag %>
88
+ # <%= file_field 'document', 'file' %>
89
+ # <%= submit_tag "Upload" %>
90
+ # <%= end_form_tag %>
91
+ #
92
+ # <%= form_tag_with_upload_progress({:action => 'add_preview'}, {:finish => 'alert(arguments[0])'}, {:action => :custom_status}) %>
93
+ # <%= upload_status_tag %>
94
+ # <%= submit_tag "Upload" %>
95
+ # <%= file_field 'preview', 'file' %>
96
+ # <%= end_form_tag %>
97
+ #
98
+ # ==== Extended Example (Controller):
99
+ #
100
+ # class DocumentController < ApplicationController
101
+ # upload_status_for :add_preview, :create, {:status => :custom_status}
102
+ #
103
+ # def add_preview
104
+ # @document = Document.find(params[:id])
105
+ # @document.preview = Preview.create(params[:preview])
106
+ # if @document.save
107
+ # finish_upload_status "'Preview added'"
108
+ # else
109
+ # finish_upload_status "'Preview not added'"
110
+ # end
111
+ # end
112
+ #
113
+ # def create
114
+ # @document = Document.new(params[:document])
115
+ #
116
+ # upload_progress.message = "Processing document..."
117
+ # session.update
118
+ #
119
+ # @document.save
120
+ # redirect_to :action => 'show', :id => @document.id
121
+ # end
122
+ #
123
+ # def custom_status
124
+ # render :inline => '<%= upload_progress_status %> <div>Updated at <%= Time.now %></div>', :layout => false
125
+ # end
126
+ #
127
+ # ==== Environment checklist
128
+ #
129
+ # This is an experimental feature that requires a specific webserver environment. Use the following checklist
130
+ # to confirm that you have an environment that supports upload progress.
131
+ #
132
+ # ===== Ruby:
133
+ #
134
+ # * Running the command `ruby -v` should print "ruby 1.8.2 (2004-12-25)" or older
135
+ #
136
+ # ===== Web server:
137
+ #
138
+ # * Apache 1.3, Apache 2.0 or Lighttpd *1.4* (need to build lighttpd from CVS)
139
+ #
140
+ # ===== FastCGI bindings:
141
+ #
142
+ # * > 0.8.6 and must be the compiled C version of the bindings
143
+ # * The command `ruby -e "p require('fcgi.so')"` should print "true"
144
+ #
145
+ # ===== Apache/Lighttpd FastCGI directives:
146
+ #
147
+ # * You must allow more than one FCGI server process to allow concurrent requests.
148
+ # * If there is only a single FCGI process you will not get the upload status updates.
149
+ # * You can check this by taking a look for running FCGI servers in your process list during a progress upload.
150
+ # * Apache directive: FastCGIConfig -minProcesses 2
151
+ # * Lighttpd directives taken from config/lighttpd.conf (min-procs):
152
+ #
153
+ # fastcgi.server = (
154
+ # ".fcgi" => (
155
+ # "APP_NAME" => (
156
+ # "socket" => "/tmp/APP_NAME1.socket",
157
+ # "bin-path" => "RAILS_ROOT/public/dispatch.fcgi",
158
+ # "min-procs" => 2
159
+ # )
160
+ # )
161
+ # )
162
+ #
163
+ # ===== config/environment.rb:
164
+ #
165
+ # * Add the following line to your config/environment.rb and restart your web server.
166
+ # * <tt>ActionController::Base.enable_upload_progress</tt>
167
+ #
168
+ # ===== Development log:
169
+ #
170
+ # * When the upload progress is enabled by you will find something the following lines:
171
+ # * "Multipart upload with progress (id: 1, size: 85464)"
172
+ # * "Finished processing multipart upload in 0.363729s"
173
+ # * If you are properly running multiple FCGI processes, then you will see multiple entries for rendering the "upload_status" action before the "Finish processing..." log entry. This is a *good thing* :)
174
+ #
175
+ module ClassMethods
176
+ # Creates an +after_filter+ which will call +finish_upload_status+
177
+ # creating the document that will be loaded into the hidden IFRAME, terminating
178
+ # the status polling forms created with +form_with_upload_progress+.
179
+ #
180
+ # Also defines an action +upload_status+ or a action name passed as
181
+ # the <tt>:status</tt> option. This status action must match the one expected
182
+ # in the +form_tag_with_upload_progress+ helper.
183
+ #
184
+ def upload_status_for(*actions)
185
+ after_filter :finish_upload_status, :only => actions
186
+
187
+ define_method(actions.last.is_a?(Hash) && actions.last[:status] || :upload_status) do
188
+ render(:inline => '<%= upload_progress_status %>', :layout => false)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Overwrites the body rendered if the upload comes from a form that tracks
194
+ # the progress of the upload. After clearing the body and any redirects, this
195
+ # method then renders the helper +finish_upload_status+
196
+ #
197
+ # This method only needs to be called if you wish to pass a
198
+ # javascript parameter to your finish event handler that you optionally
199
+ # define in +form_with_upload_progress+
200
+ #
201
+ # === Parameter:
202
+ #
203
+ # client_js_argument:: a string containing a Javascript expression that will
204
+ # be evaluated and passed to your +finish+ handler of
205
+ # +form_tag_with_upload_progress+.
206
+ #
207
+ # You can pass a String, Number or Boolean.
208
+ #
209
+ # === Strings
210
+ #
211
+ # Strings contain Javascript code that will be evaluated on the client. If you
212
+ # wish to pass a string to the client finish callback, you will need to include
213
+ # quotes in the +client_js_argument+ you pass to this method.
214
+ #
215
+ # ==== Example
216
+ #
217
+ # finish_upload_status("\"Finished\"")
218
+ # finish_upload_status("'Finished #{@document.title}'")
219
+ # finish_upload_status("{success: true, message: 'Done!'}")
220
+ # finish_upload_status("function() { alert('Uploaded!'); }")
221
+ #
222
+ # === Numbers / Booleans
223
+ #
224
+ # Numbers and Booleans can either be passed as Number objects or string versions
225
+ # of number objects as they are evaluated by Javascript the same way as in Ruby.
226
+ #
227
+ # ==== Example
228
+ #
229
+ # finish_upload_status(0)
230
+ # finish_upload_status(@document.file.size)
231
+ # finish_upload_status("10")
232
+ #
233
+ # === Nil
234
+ #
235
+ # To pass +nil+ to the finish callback, use a string "undefined"
236
+ #
237
+ # ==== Example
238
+ #
239
+ # finish_upload_status(@message || "undefined")
240
+ #
241
+ # == Redirection
242
+ #
243
+ # If you action performs a redirection then +finish_upload_status+ will recognize
244
+ # the redirection and properly create the Javascript to perform the redirection in
245
+ # the proper location.
246
+ #
247
+ # It is possible to redirect and pass a parameter to the finish callback.
248
+ #
249
+ # ==== Example
250
+ #
251
+ # redirect_to :action => 'show', :id => @document.id
252
+ # finish_upload_status("'Redirecting you to your new file'")
253
+ #
254
+ #
255
+ def finish_upload_status(client_js_argument='')
256
+ if not @rendered_finish_upload_status and params[:upload_id]
257
+ @rendered_finish_upload_status = true
258
+
259
+ erase_render_results
260
+ location = erase_redirect_results || ''
261
+
262
+ ## TODO determine if #inspect is the appropriate way to marshall values
263
+ ## in inline templates
264
+
265
+ template = "<%= finish_upload_status({"
266
+ template << ":client_js_argument => #{client_js_argument.inspect}, "
267
+ template << ":redirect_to => #{location.to_s.inspect}, "
268
+ template << "}) %>"
269
+
270
+ render({ :inline => template, :layout => false })
271
+ end
272
+ end
273
+
274
+ # Returns and saves the next unique +upload_id+ in the instance variable
275
+ # <tt>@upload_id</tt>
276
+ def next_upload_id
277
+ @upload_id = last_upload_id.succ
278
+ end
279
+
280
+ # Either returns the last saved +upload_id+ or looks in the session
281
+ # for the last used +upload_id+ and saves it as the intance variable
282
+ # <tt>@upload_id</tt>
283
+ def last_upload_id
284
+ @upload_id ||= ((session[:uploads] || {}).keys.map{|k| k.to_i}.sort.last || 0).to_s
285
+ end
286
+
287
+ # Returns the +upload_id+ from the query parameters or if it cannot be found
288
+ # in the query parameters, then return the +last_upload_id+
289
+ def current_upload_id
290
+ params[:upload_id] or last_upload_id
291
+ end
292
+
293
+ # Get the UploadProgress::Progress object for the supplied +upload_id+ from the
294
+ # session. If no +upload_id+ is given, then use the +current_upload_id+
295
+ #
296
+ # If an UploadProgress::Progress object cannot be found, a new instance will be
297
+ # returned with <code>total_bytes == 0</code>, <code>started? == false</code>,
298
+ # and <code>finished? == true</code>.
299
+ def upload_progress(upload_id = nil)
300
+ upload_id ||= current_upload_id
301
+ session[:uploads] && session[:uploads][upload_id] || UploadProgress::Progress.new(0)
302
+ end
303
+ end
@@ -0,0 +1,424 @@
1
+ module UploadProgress
2
+ # Provides a set of methods to be used in your views to help with the
3
+ # rendering of Ajax enabled status updating during a multipart upload.
4
+ #
5
+ # The basic mechanism for upload progress is that the form will post to a
6
+ # hidden <iframe> element, then poll a status action that will replace the
7
+ # contents of a status container. Client Javascript hooks are provided for
8
+ # +begin+ and +finish+ of the update.
9
+ #
10
+ # If you wish to have a DTD that will validate this page, use XHTML
11
+ # Transitional because this DTD supports the <iframe> element.
12
+ #
13
+ # == Typical usage
14
+ #
15
+ # In your upload view:
16
+ #
17
+ # <%= form_tag_with_upload_progress({ :action => 'create' }) %>
18
+ # <%= file_field "document", "file" %>
19
+ # <%= submit_tag "Upload" %>
20
+ # <%= upload_status_tag %>
21
+ # <%= end_form_tag %>
22
+ #
23
+ # In your controller:
24
+ #
25
+ # class DocumentController < ApplicationController
26
+ # upload_status_for :create
27
+ #
28
+ # def create
29
+ # # ... Your document creation action
30
+ # end
31
+ # end
32
+ #
33
+ # == Javascript callback on begin and finished
34
+ #
35
+ # In your upload view:
36
+ #
37
+ # <%= form_tag_with_upload_progress({ :action => 'create' }, {
38
+ # :begin => "alert('upload beginning'),
39
+ # :finish => "alert('upload finished')}) %>
40
+ # <%= file_field "document", "file" %>
41
+ # <%= submit_tag "Upload" %>
42
+ # <%= upload_status_tag %>
43
+ # <%= end_form_tag %>
44
+ #
45
+ #
46
+ # == CSS Styling of the status text and progress bar
47
+ #
48
+ # See +upload_status_text_tag+ and +upload_status_progress_bar_tag+ for references
49
+ # of the IDs and CSS classes used.
50
+ #
51
+ # Default styling is included with the scaffolding CSS.
52
+ module UploadProgressHelper
53
+ unless const_defined? :FREQUENCY
54
+ # Default number of seconds between client updates
55
+ FREQUENCY = 2.0
56
+
57
+ # Factor to decrease the frequency when the +upload_status+ action returns the same results
58
+ # To disable update decay, set this constant to +false+
59
+ FREQUENCY_DECAY = 1.8
60
+ end
61
+
62
+ # Contains a hash of status messages used for localization of
63
+ # +upload_progress_status+ and +upload_progress_text+. Each string is
64
+ # evaluated in the helper method context so you can include your own
65
+ # calculations and string iterpolations.
66
+ #
67
+ # The following keys are defined:
68
+ #
69
+ # <tt>:begin</tt>:: Displayed before the first byte is received on the server
70
+ # <tt>:update</tt>:: Contains a human representation of the upload progress
71
+ # <tt>:finish</tt>:: Displayed when the file upload is complete, before the action has completed. If you are performing extra activity in your action such as processing of the upload, then inform the user of what you are doing by setting +upload_progress.message+
72
+ #
73
+ @@default_messages = {
74
+ :begin => '"Upload starting..."',
75
+ :update => '"#{human_size(upload_progress.received_bytes)} of #{human_size(upload_progress.total_bytes)} at #{human_size(upload_progress.bitrate)}/s; #{distance_of_time_in_words(0,upload_progress.remaining_seconds,true)} remaining"',
76
+ :finish => 'upload_progress.message.blank? ? "Upload finished." : upload_progress.message',
77
+ }
78
+
79
+
80
+ # Creates a form tag and hidden <iframe> necessary for the upload progress
81
+ # status messages to be displayed in a designated +div+ on your page.
82
+ #
83
+ # == Initializations
84
+ #
85
+ # When the upload starts, the content created by +upload_status_tag+ will be filled out with
86
+ # "Upload starting...". When the upload is finished, "Upload finished." will be used. Every
87
+ # update inbetween the begin and finish events will be determined by the server +upload_status+
88
+ # action. Doing this automatically means that the user can use the same form to upload multiple
89
+ # files without refreshing page while still displaying a reasonable progress.
90
+ #
91
+ # == Upload IDs
92
+ #
93
+ # For the view and the controller to know about the same upload they must share
94
+ # a common +upload_id+. +form_tag_with_upload_progress+ prepares the next available
95
+ # +upload_id+ when called. There are other methods which use the +upload_id+ so the
96
+ # order in which you include your content is important. Any content that depends on the
97
+ # +upload_id+ will use the one defined +form_tag_with_upload_progress+
98
+ # otherwise you will need to explicitly declare the +upload_id+ shared among
99
+ # your progress elements.
100
+ #
101
+ # Status container after the form:
102
+ #
103
+ # <%= form_tag_with_upload_progress %>
104
+ # <%= end_form_tag %>
105
+ #
106
+ # <%= upload_status_tag %>
107
+ #
108
+ # Status container before form:
109
+ #
110
+ # <% my_upload_id = next_upload_id %>
111
+ #
112
+ # <%= upload_status_tag %>
113
+ #
114
+ # <%= form_tag_with_upload_progress :upload_id => my_upload_id %>
115
+ # <%= end_form_tag %>
116
+ #
117
+ # It is recommended that the helpers manage the +upload_id+ parameter.
118
+ #
119
+ # == Options
120
+ #
121
+ # +form_tag_with_upload_progress+ uses similar options as +form_tag+
122
+ # yet accepts another hash for the options used for the +upload_status+ action.
123
+ #
124
+ # <tt>url_for_options</tt>:: The same options used by +form_tag+ including:
125
+ # <tt>:upload_id</tt>:: the upload id used to uniquely identify this upload
126
+ #
127
+ # <tt>options</tt>:: similar options to +form_tag+ including:
128
+ # <tt>:begin</tt>:: Javascript code that executes before the first status update occurs.
129
+ # <tt>:finish</tt>:: Javascript code that executes after the action that receives the post returns.
130
+ # <tt>:frequency</tt>:: number of seconds between polls to the upload status action.
131
+ #
132
+ # <tt>status_url_for_options</tt>:: options passed to +url_for+ to build the url
133
+ # for the upload status action.
134
+ # <tt>:controller</tt>:: defines the controller to be used for a custom update status action
135
+ # <tt>:action</tt>:: defines the action to be used for a custom update status action
136
+ #
137
+ # Parameters passed to the action defined by status_url_for_options
138
+ #
139
+ # <tt>:upload_id</tt>:: the upload_id automatically generated by +form_tag_with_upload_progress+ or the user defined id passed to this method.
140
+ #
141
+ def form_tag_with_upload_progress(url_for_options = {}, options = {}, status_url_for_options = {}, *parameters_for_url_method)
142
+
143
+ ## Setup the action URL and the server-side upload_status action for
144
+ ## polling of status during the upload
145
+
146
+ options = options.dup
147
+
148
+ upload_id = url_for_options.delete(:upload_id) || next_upload_id
149
+ upload_action_url = url_for(url_for_options)
150
+
151
+ if status_url_for_options.is_a? Hash
152
+ status_url_for_options = status_url_for_options.merge({
153
+ :action => 'upload_status',
154
+ :upload_id => upload_id})
155
+ end
156
+
157
+ status_url = url_for(status_url_for_options)
158
+
159
+ ## Prepare the form options. Dynamically change the target and URL to enable the status
160
+ ## updating only if javascript is enabled, otherwise perform the form submission in the same
161
+ ## frame.
162
+
163
+ upload_target = options[:target] || upload_target_id
164
+ upload_id_param = "#{upload_action_url.include?('?') ? '&' : '?'}upload_id=#{upload_id}"
165
+
166
+ ## Externally :begin and :finish are the entry and exit points
167
+ ## Internally, :finish is called :complete
168
+
169
+ js_options = {
170
+ :decay => options[:decay] || FREQUENCY_DECAY,
171
+ :frequency => options[:frequency] || FREQUENCY,
172
+ }
173
+
174
+ updater_options = '{' + js_options.map {|k, v| "#{k}:#{v}"}.sort.join(',') + '}'
175
+
176
+ ## Finish off the updating by forcing the progress bar to 100% and status text because the
177
+ ## results of the post may load and finish in the IFRAME before the last status update
178
+ ## is loaded.
179
+
180
+ options[:complete] = "$('#{status_tag_id}').innerHTML='#{escape_javascript upload_progress_text(:finish)}';"
181
+ options[:complete] << "#{upload_progress_update_bar_js(100)};"
182
+ options[:complete] << "#{upload_update_object} = null"
183
+ options[:complete] = "#{options[:complete]}; #{options[:finish]}" if options[:finish]
184
+
185
+ options[:script] = true
186
+
187
+ ## Prepare the periodic updater, clearing any previous updater
188
+
189
+ updater = "if (#{upload_update_object}) { #{upload_update_object}.stop(); }"
190
+ updater << "#{upload_update_object} = new Ajax.PeriodicalUpdater('#{status_tag_id}',"
191
+ updater << "'#{status_url}', Object.extend(#{options_for_ajax(options)},#{updater_options}))"
192
+
193
+ updater = "#{options[:begin]}; #{updater}" if options[:begin]
194
+ updater = "#{upload_progress_update_bar_js(0)}; #{updater}"
195
+ updater = "$('#{status_tag_id}').innerHTML='#{escape_javascript upload_progress_text(:begin)}'; #{updater}"
196
+
197
+ ## Touch up the form action and target to use the given target instead of the
198
+ ## default one. Then start the updater
199
+
200
+ options[:onsubmit] = "if (this.action.indexOf('upload_id') < 0){ this.action += '#{escape_javascript upload_id_param}'; }"
201
+ options[:onsubmit] << "this.target = '#{escape_javascript upload_target}';"
202
+ options[:onsubmit] << "#{updater}; return true"
203
+ options[:multipart] = true
204
+
205
+ [:begin, :finish, :complete, :frequency, :decay, :script].each { |sym| options.delete(sym) }
206
+
207
+ ## Create the tags
208
+ ## If a target for the form is given then avoid creating the hidden IFRAME
209
+
210
+ tag = form_tag(upload_action_url, options, *parameters_for_url_method)
211
+
212
+ unless options[:target]
213
+ tag << content_tag('iframe', '', {
214
+ :id => upload_target,
215
+ :name => upload_target,
216
+ :src => '',
217
+ :style => 'width:0px;height:0px;border:0'
218
+ })
219
+ end
220
+
221
+ tag
222
+ end
223
+
224
+ # This method must be called by the action that receives the form post
225
+ # with the +upload_progress+. By default this method is rendered when
226
+ # the controller declares that the action is the receiver of a
227
+ # +form_tag_with_upload_progress+ posting.
228
+ #
229
+ # This template will do a javascript redirect to the URL specified in +redirect_to+
230
+ # if this method is called anywhere in the controller action. When the action
231
+ # performs a redirect, the +finish+ handler will not be called.
232
+ #
233
+ # If there are errors in the action then you should set the controller
234
+ # instance variable +@errors+. The +@errors+ object will be
235
+ # converted to a javascript array from +@errors.full_messages+ and
236
+ # passed to the +finish+ handler of +form_tag_with_upload_progress+
237
+ #
238
+ # If no errors have occured, the parameter to the +finish+ handler will
239
+ # be +undefined+.
240
+ #
241
+ # == Example (in view)
242
+ #
243
+ # <script>
244
+ # function do_finish(errors) {
245
+ # if (errors) {
246
+ # alert(errors);
247
+ # }
248
+ # }
249
+ # </script>
250
+ #
251
+ # <%= form_tag_with_upload_progress {:action => 'create'}, {finish => 'do_finish(arguments[0])'} %>
252
+ #
253
+ def finish_upload_status(options = {})
254
+ # Always trigger the stop/finish callback
255
+ js = "parent.#{upload_update_object}.stop(#{options[:client_js_argument]});\n"
256
+
257
+ # Redirect if redirect_to was called in controller
258
+ js << "parent.location.replace('#{escape_javascript options[:redirect_to]}');\n" unless options[:redirect_to].blank?
259
+
260
+ # Guard against multiple triggers/redirects on back
261
+ js = "if (parent.#{upload_update_object}) { #{js} }\n"
262
+
263
+ content_tag("html",
264
+ content_tag("head",
265
+ content_tag("script", "function finish() { #{js} }",
266
+ {:type => "text/javascript", :language => "javascript"})) +
267
+ content_tag("body", '', :onload => 'finish()'))
268
+ end
269
+
270
+ # Renders the HTML to contain the upload progress bar above the
271
+ # default messages
272
+ #
273
+ # Use this method to display the upload status after your +form_tag_with_upload_progress+
274
+ def upload_status_tag(content='', options={})
275
+ upload_status_progress_bar_tag + upload_status_text_tag(content, options)
276
+ end
277
+
278
+ # Content helper that will create a +div+ with the proper ID and class that
279
+ # will contain the contents returned by the +upload_status+ action. The container
280
+ # is defined as
281
+ #
282
+ # <div id="#{status_tag_id}" class="uploadStatus"> </div>
283
+ #
284
+ # Style this container by selecting the +.uploadStatus+ +CSS+ class.
285
+ #
286
+ # The +content+ parameter will be included in the inner most +div+ when
287
+ # rendered.
288
+ #
289
+ # The +options+ will create attributes on the outer most div. To use a different
290
+ # +CSS+ class, pass a different class option.
291
+ #
292
+ # Example +CSS+:
293
+ # .uploadStatus { font-size: 10px; color: grey; }
294
+ #
295
+ def upload_status_text_tag(content=nil, options={})
296
+ content_tag("div", content, {:id => status_tag_id, :class => 'uploadStatus'}.merge(options))
297
+ end
298
+
299
+ # Content helper that will create the element tree that can be easily styled
300
+ # with +CSS+ to create a progress bar effect. The containers are defined as:
301
+ #
302
+ # <div class="progressBar" id="#{progress_bar_id}">
303
+ # <div class="border">
304
+ # <div class="background">
305
+ # <div class="content"> </div>
306
+ # </div>
307
+ # </div>
308
+ # </div>
309
+ #
310
+ # The +content+ parameter will be included in the inner most +div+ when
311
+ # rendered.
312
+ #
313
+ # The +options+ will create attributes on the outer most div. To use a different
314
+ # +CSS+ class, pass a different class option.
315
+ #
316
+ # Example:
317
+ # <%= upload_status_progress_bar_tag('', {:class => 'progress'}) %>
318
+ #
319
+ # Example +CSS+:
320
+ #
321
+ # div.progressBar {
322
+ # margin: 5px;
323
+ # }
324
+ #
325
+ # div.progressBar div.border {
326
+ # background-color: #fff;
327
+ # border: 1px solid grey;
328
+ # width: 100%;
329
+ # }
330
+ #
331
+ # div.progressBar div.background {
332
+ # background-color: #333;
333
+ # height: 18px;
334
+ # width: 0%;
335
+ # }
336
+ #
337
+ def upload_status_progress_bar_tag(content='', options={})
338
+ css = [options[:class], 'progressBar'].compact.join(' ')
339
+
340
+ content_tag("div",
341
+ content_tag("div",
342
+ content_tag("div",
343
+ content_tag("div", content, :class => 'foreground'),
344
+ :class => 'background'),
345
+ :class => 'border'),
346
+ {:id => progress_bar_id}.merge(options).merge({:class => css}))
347
+ end
348
+
349
+ # The text and Javascript returned by the default +upload_status+ controller
350
+ # action which will replace the contents of the div created by +upload_status_text_tag+
351
+ # and grow the progress bar background to the appropriate width.
352
+ #
353
+ # See +upload_progress_text+ and +upload_progress_update_bar_js+
354
+ def upload_progress_status
355
+ "#{upload_progress_text}<script>#{upload_progress_update_bar_js}</script>"
356
+ end
357
+
358
+ # Javascript helper that will create a script that will change the width
359
+ # of the background progress bar container. Include this in the script
360
+ # portion of your view rendered by your +upload_status+ action to
361
+ # automatically find and update the progress bar.
362
+ #
363
+ # Example (in controller):
364
+ #
365
+ # def upload_status
366
+ # render :inline => "<script><%= update_upload_progress_bar_js %></script>", :layout => false
367
+ # end
368
+ #
369
+ #
370
+ def upload_progress_update_bar_js(percent=nil)
371
+ progress = upload_progress
372
+ percent ||= case
373
+ when progress.nil? || !progress.started? then 0
374
+ when progress.finished? then 100
375
+ else progress.completed_percent
376
+ end.to_i
377
+
378
+ # TODO do animation instead of jumping
379
+ "if($('#{progress_bar_id}')){$('#{progress_bar_id}').firstChild.firstChild.style.width='#{percent}%'}"
380
+ end
381
+
382
+ # Generates a nicely formatted string of current upload progress for
383
+ # +UploadProgress::Progress+ object +progress+. Addtionally, it
384
+ # will return "Upload starting..." if progress has not been initialized,
385
+ # "Receiving data..." if there is no received data yet, and "Upload
386
+ # finished" when all data has been sent.
387
+ #
388
+ # You can overload this method to add you own output to the
389
+ #
390
+ # Example return: 223.5 KB of 1.5 MB at 321.2 KB/s; less than 10 seconds
391
+ # remaining
392
+ def upload_progress_text(state=nil)
393
+ eval case
394
+ when state then @@default_messages[state.to_sym]
395
+ when upload_progress.nil? || !upload_progress.started? then @@default_messages[:begin]
396
+ when upload_progress.finished? then @@default_messages[:finish]
397
+ else @@default_messages[:update]
398
+ end
399
+ end
400
+
401
+ protected
402
+ # Javascript object used to contain the polling methods and keep track of
403
+ # the finished state
404
+ def upload_update_object
405
+ "document.uploadStatus#{current_upload_id}"
406
+ end
407
+
408
+ # Element ID of the progress bar
409
+ def progress_bar_id
410
+ "UploadProgressBar#{current_upload_id}"
411
+ end
412
+
413
+ # Element ID of the progress status container
414
+ def status_tag_id
415
+ "UploadStatus#{current_upload_id}"
416
+ end
417
+
418
+ # Element ID of the target <iframe> used as the target of the form
419
+ def upload_target_id
420
+ "UploadTarget#{current_upload_id}"
421
+ end
422
+
423
+ end
424
+ end