zena 1.0.0.beta3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (418) hide show
  1. data/History.txt +29 -0
  2. data/Rakefile +2 -0
  3. data/TODO_ZENA_1_0 +13 -23
  4. data/app/controllers/columns_controller.rb +1 -1
  5. data/app/controllers/comments_controller.rb +4 -3
  6. data/app/controllers/documents_controller.rb +8 -11
  7. data/app/controllers/nodes_controller.rb +39 -21
  8. data/app/controllers/users_controller.rb +8 -3
  9. data/app/controllers/versions_controller.rb +2 -2
  10. data/app/controllers/virtual_classes_controller.rb +17 -11
  11. data/app/helpers/documents_helper.rb +0 -3
  12. data/app/helpers/users_helper.rb +17 -0
  13. data/app/models/cache.rb +36 -31
  14. data/app/models/column.rb +48 -5
  15. data/app/models/comment.rb +14 -5
  16. data/app/models/data_entry.rb +2 -2
  17. data/app/models/document.rb +23 -33
  18. data/app/models/idx_nodes_datetime.rb +4 -0
  19. data/app/models/idx_nodes_float.rb +4 -0
  20. data/app/models/idx_project.rb +3 -0
  21. data/app/models/node.rb +372 -308
  22. data/app/models/page.rb +1 -31
  23. data/app/models/relation.rb +4 -4
  24. data/app/models/relation_proxy.rb +128 -17
  25. data/app/models/role.rb +27 -2
  26. data/app/models/site.rb +64 -56
  27. data/app/models/template.rb +11 -12
  28. data/app/models/text_document.rb +6 -7
  29. data/app/models/user.rb +95 -46
  30. data/app/models/version.rb +2 -2
  31. data/app/models/virtual_class.rb +418 -73
  32. data/app/views/columns/_form.html.erb +1 -1
  33. data/app/views/columns/_li.html.erb +1 -1
  34. data/app/views/comments/_form.rhtml +1 -1
  35. data/app/views/comments/_li.rhtml +1 -1
  36. data/app/views/comments/_li_simple.rhtml +1 -1
  37. data/app/views/groups/_form.rhtml +1 -1
  38. data/app/views/links/_li.rhtml +1 -1
  39. data/app/views/nodes/_groups.rhtml +1 -1
  40. data/app/views/nodes/_import_results.rhtml +1 -1
  41. data/app/views/nodes/_parent.rhtml +1 -1
  42. data/app/views/nodes/_results.rhtml +1 -1
  43. data/app/views/nodes/create.rjs +4 -2
  44. data/app/views/relations/_li.erb +2 -2
  45. data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
  46. data/app/views/templates/document_create_tabs/_template.rhtml +2 -2
  47. data/app/views/templates/document_create_tabs/_text_document.rhtml +2 -2
  48. data/app/views/templates/edit_tabs/_help.rhtml +1 -1
  49. data/app/views/templates/edit_tabs/_title.rhtml +0 -3
  50. data/app/views/users/_form.rhtml +2 -6
  51. data/app/views/users/_li.rhtml +1 -3
  52. data/app/views/users/create.rjs +4 -4
  53. data/app/views/users/preferences.html.erb +1 -4
  54. data/app/views/versions/custom_tab.rhtml +5 -0
  55. data/app/views/virtual_classes/_form.erb +20 -10
  56. data/app/views/virtual_classes/_li.erb +21 -8
  57. data/app/views/zafu/default/Node-+search.zafu +1 -1
  58. data/app/views/zafu/default/Node.zafu +3 -3
  59. data/bricks/captcha/lib/bricks/captcha.rb +1 -1
  60. data/bricks/mongrel/zena/deploy.rb +14 -0
  61. data/bricks/{data2pdf → pdf}/.document +0 -0
  62. data/bricks/pdf/README +33 -0
  63. data/bricks/{data2pdf → pdf}/Rakefile +0 -0
  64. data/bricks/pdf/VERSION +1 -0
  65. data/bricks/pdf/lib/bricks/pdf.rb +110 -0
  66. data/bricks/pdf/lib/bricks/pdf/engine/prince.rb +38 -0
  67. data/bricks/pdf/lib/bricks/pdf/engine/xhtml2pdf.rb +9 -0
  68. data/bricks/pdf/lib/bricks/pdf/install.rb +121 -0
  69. data/bricks/pdf/test/engines/test_prince.rb +15 -0
  70. data/bricks/pdf/test/engines/test_xhtml2pdf.rb +15 -0
  71. data/bricks/{data2pdf → pdf}/test/fixtures/application.css +0 -0
  72. data/bricks/{data2pdf → pdf}/test/fixtures/contact.html +0 -0
  73. data/bricks/{data2pdf → pdf}/test/fixtures/pisa-default.css +0 -0
  74. data/bricks/{data2pdf → pdf}/test/fixtures/sheet1.css +0 -0
  75. data/bricks/{data2pdf → pdf}/test/fixtures/sheet2.css +0 -0
  76. data/bricks/{data2pdf → pdf}/test/fixtures/simple-html.html +0 -0
  77. data/bricks/{data2pdf → pdf}/test/fixtures/simple-text.txt +0 -0
  78. data/bricks/{data2pdf → pdf}/test/helper.rb +4 -5
  79. data/bricks/pdf/test/shoulda_macros/shoulda_pdf.rb +72 -0
  80. data/bricks/pdf/zena/init.rb +5 -0
  81. data/bricks/pdf/zena/tasks.rb +17 -0
  82. data/bricks/sphinx/lib/{use_sphinx.rb → bricks/sphinx.rb} +1 -1
  83. data/bricks/tags/zena/init.rb +2 -2
  84. data/bricks/tags/zena/test/zafu/tags.yml +4 -4
  85. data/bricks/zena/zena/migrate/01_base.rb +482 -0
  86. data/config/bricks.yml +22 -6
  87. data/config/gems.yml +8 -6
  88. data/db/20100628074512_zena0x_to1x.rb +6 -1
  89. data/db/fix/024_correct_vclass_kpath.rb +11 -0
  90. data/db/fix/025_move_tag_into_vclass.rb +13 -0
  91. data/db/{migrate → fix}/026_rename_templates.rb +0 -0
  92. data/db/{migrate → fix}/045_avoid_star_in_templates.rb +0 -0
  93. data/db/{migrate → fix}/046_fix_zazen_image_tag.rb +0 -0
  94. data/db/{migrate → fix}/047_change_default_link_id_to_zero.rb +1 -3
  95. data/db/{migrate → fix}/049_fix_publish_from_is_null.rb +0 -0
  96. data/db/{migrate → fix}/20090924141459_zafu_fix_sept09.rb +0 -0
  97. data/db/{migrate → fix}/20091013100351_rename_publish_group_to_drive_group.rb +1 -3
  98. data/db/{migrate → fix}/20091124161608_rebuild_fullpath.rb +0 -1
  99. data/db/{migrate → fix}/20100115134729_rebuild_fullpath_after_fix.rb +0 -0
  100. data/db/{migrate → fix}/20100526090140_renamed_contact_model_to_base_contact.rb +2 -4
  101. data/db/{migrate → fix/old_migrations}/001_create_base.rb +0 -1
  102. data/db/{migrate → fix/old_migrations}/002_add_time_zone_to_users.rb +0 -0
  103. data/db/{migrate → fix/old_migrations}/003_add_custom_base_flag.rb +0 -0
  104. data/db/{migrate → fix/old_migrations}/004_rename_template_skin.rb +0 -0
  105. data/db/{migrate → fix/old_migrations}/005_create_cached_pages.rb +0 -0
  106. data/db/{migrate → fix/old_migrations}/006_create_sites.rb +0 -0
  107. data/db/{migrate → fix/old_migrations}/007_replace_id_by_zip.rb +0 -0
  108. data/db/{migrate → fix/old_migrations}/008_user_status.rb +0 -0
  109. data/db/{migrate → fix/old_migrations}/009_fulltext.rb +0 -0
  110. data/db/fix/old_migrations/010_create_template_content.rb +17 -0
  111. data/db/{migrate → fix/old_migrations}/011_project_to_section.rb +0 -0
  112. data/db/{migrate → fix/old_migrations}/012_add_project_id.rb +0 -0
  113. data/db/{migrate → fix/old_migrations}/013_remove_defaults.rb +0 -0
  114. data/db/{migrate → fix/old_migrations}/014_add_sort_field.rb +0 -0
  115. data/db/{migrate → fix/old_migrations}/015_add_dyn_attributes.rb +0 -0
  116. data/db/{migrate → fix/old_migrations}/016_remove_translations.rb +0 -0
  117. data/db/{migrate → fix/old_migrations}/017_rename_authorize.rb +0 -0
  118. data/db/{migrate → fix/old_migrations}/018_add_auth_option.rb +0 -0
  119. data/db/{migrate → fix/old_migrations}/019_remove_user_status.rb +0 -0
  120. data/db/{migrate → fix/old_migrations}/020_create_participation.rb +0 -0
  121. data/db/{migrate → fix/old_migrations}/021_create_relations.rb +0 -0
  122. data/db/{migrate → fix/old_migrations}/022_create_virtual_classes.rb +0 -0
  123. data/db/{migrate → fix/old_migrations}/023_ip_on_anonymous_comment.rb +0 -0
  124. data/db/{migrate → fix/old_migrations}/027_add_country_to_contacts.rb +0 -0
  125. data/db/{migrate → fix/old_migrations}/028_change_size_of_conten_type_field.rb +0 -0
  126. data/db/{migrate → fix/old_migrations}/029_create_data_entries.rb +0 -0
  127. data/db/{migrate → fix/old_migrations}/030_redit_auto_publish_site_settings.rb +0 -0
  128. data/db/{migrate → fix/old_migrations}/031_create_iformats.rb +0 -0
  129. data/db/{migrate → fix/old_migrations}/032_caches_context_as_hash.rb +0 -0
  130. data/db/{migrate → fix/old_migrations}/033_documents_kpath_change.rb +0 -0
  131. data/db/{migrate → fix/old_migrations}/034_change_file_storage.rb +0 -0
  132. data/db/{migrate → fix/old_migrations}/035_add_status_to_link.rb +0 -0
  133. data/db/{migrate → fix/old_migrations}/036_add_flag_fields_on_nodes.rb +0 -0
  134. data/db/{migrate → fix/old_migrations}/037_add_auto_create_discussion_to_v_class.rb +0 -0
  135. data/db/{migrate → fix/old_migrations}/038_create_site_attributes.rb +0 -0
  136. data/db/{migrate → fix/old_migrations}/039_default_position.rb +0 -0
  137. data/db/{migrate → fix/old_migrations}/040_second_value_for_data_entry.rb +0 -0
  138. data/db/{migrate → fix/old_migrations}/041_add_attributes_to_v_class.rb +0 -0
  139. data/db/{migrate → fix/old_migrations}/042_fix_position_should_be_float.rb +0 -0
  140. data/db/{migrate → fix/old_migrations}/043_move_user_lang_into_participation.rb +0 -0
  141. data/db/{migrate → fix/old_migrations}/044_remove_monolingual_site_option.rb +0 -0
  142. data/db/{migrate → fix/old_migrations}/048_link_source_target_can_be_null.rb +0 -0
  143. data/db/{migrate → fix/old_migrations}/050_date_in_links.rb +0 -0
  144. data/db/{migrate → fix/old_migrations}/051_add_exif_tags_to_images.rb +0 -0
  145. data/db/{migrate → fix/old_migrations}/20090825201159_insert_zero_link.rb +0 -0
  146. data/db/{migrate → fix/old_migrations}/20090825201200_merge_bricks_migrations_with_std_migrations.rb +0 -0
  147. data/db/{migrate → fix/old_migrations}/20090927125912_allow_null_in_text_fields.rb +0 -0
  148. data/db/{migrate → fix/old_migrations}/20090928133440_no_more_private_nodes.rb +0 -0
  149. data/db/{migrate → fix/old_migrations}/20090928143754_version_status_change.rb +0 -0
  150. data/db/{migrate → fix/old_migrations}/20091001084025_change_status_values_for_comments.rb +0 -0
  151. data/db/{migrate → fix/old_migrations}/20091009084057_add_vhash_in_node.rb +0 -0
  152. data/db/{migrate → fix/old_migrations}/20091014130833_fix_template_title.rb +0 -0
  153. data/db/{migrate → fix/old_migrations}/20091014183726_merge_participation_into_users.rb +0 -0
  154. data/db/{migrate → fix/old_migrations}/20091018200734_add_popup_info_to_image_format.rb +0 -0
  155. data/db/{migrate → fix/old_migrations}/20091026161708_add_persistence_token.rb +0 -0
  156. data/db/{migrate → fix/old_migrations}/20091101184952_add_session_table.rb +0 -0
  157. data/db/{migrate → fix/old_migrations}/20091123175137_add_single_access_token.rb +0 -0
  158. data/db/{migrate → fix/old_migrations}/20100125062254_add_dynamo_to_version.rb +0 -0
  159. data/db/{migrate → fix/old_migrations}/20100201133242_remove_default_status_on_version.rb +0 -0
  160. data/db/{migrate → fix/old_migrations}/20100208194210_create_attachments.rb +0 -0
  161. data/db/{migrate → fix/old_migrations}/20100210112319_change_dynamo_to_property.rb +0 -0
  162. data/db/{migrate → fix/old_migrations}/20100320145726_transform_template_contents_into_index.rb +0 -0
  163. data/db/{migrate → fix/old_migrations}/20100328125634_change_skin_name_to_id.rb +0 -0
  164. data/db/{migrate → fix/old_migrations}/20100417061257_add_properties_to_sites.rb +0 -0
  165. data/db/{migrate → fix/old_migrations}/20100419163149_rename_name_to_node_name.rb +0 -0
  166. data/db/{migrate → fix/old_migrations}/20100422091606_change_v_class_table_into_roles.rb +0 -0
  167. data/db/{migrate → fix/old_migrations}/20100422094048_node_habtm_roles.rb +0 -0
  168. data/db/{migrate → fix/old_migrations}/20100422115935_create_columns.rb +0 -0
  169. data/db/{migrate → fix/old_migrations}/20100513181529_add_site_id_to_columns.rb +0 -0
  170. data/db/{migrate → fix/old_migrations}/20100519091711_add_index_definition_to_columns.rb +0 -0
  171. data/db/{migrate → fix/old_migrations}/20100519091940_create_idx_nodes_string.rb +0 -0
  172. data/db/{migrate → fix/old_migrations}/20100519232432_create_idx_nodes_ml_string.rb +0 -0
  173. data/db/{migrate → fix/old_migrations}/20100525113858_add_porperties_to_users.rb +0 -0
  174. data/db/{migrate → fix/old_migrations}/20100527130937_change_column_index_to_string.rb +0 -0
  175. data/db/{migrate → fix/old_migrations}/20100531135128_add_fulltext_builder_fields.rb +0 -0
  176. data/db/{migrate → fix/old_migrations}/20100915062903_add_api_group_id_to_site.rb +0 -0
  177. data/db/fix/old_migrations/20100923154807_remove_base_contact.rb +84 -0
  178. data/db/fix/old_migrations/20100926192223_remove_su_user.rb +8 -0
  179. data/db/fix/old_migrations/20100927141658_add_eval_attributes_to_v_class.rb +12 -0
  180. data/db/fix/old_migrations/20100928185257_add_obvious_idx.rb +52 -0
  181. data/db/fix/old_migrations/20100929143111_remove_node_name.rb +11 -0
  182. data/db/fix/old_migrations/20101006090454_store_properties_in_long_text.rb +9 -0
  183. data/db/fix/old_migrations/20101014185753_remove_user_prototype_id.rb +9 -0
  184. data/db/fix/old_migrations/20101101084318_create_scope_index.rb +35 -0
  185. data/db/fix/old_migrations/20101109074232_create_idx_nodes_tables.rb +65 -0
  186. data/db/fix/old_migrations/20101110184235_add_role_update_to_site.rb +9 -0
  187. data/db/fix/old_migrations/20101116103920_change_scope_index.rb +31 -0
  188. data/db/fix/old_migrations/20101123125822_add_integer_idx.rb +17 -0
  189. data/db/fix/old_migrations/20101130134522_add_index_field.rb +13 -0
  190. data/db/fix/old_migrations/20101213133816_add_group_to_relation.rb +9 -0
  191. data/db/init/base/help.fr.zml +1 -1
  192. data/db/init/base/skins/default.zml +0 -1
  193. data/db/init/base/skins/default/Node-+search.zafu +1 -1
  194. data/db/init/base/skins/default/Node-tree.zafu +3 -3
  195. data/db/init/base/skins/default/Node.zafu +3 -3
  196. data/lib/bricks/loader.rb +4 -1
  197. data/lib/bricks/requirements_validation.rb +11 -6
  198. data/lib/log_recorder/lib/log_recorder.rb +2 -2
  199. data/lib/tasks/zena.rake +25 -15
  200. data/lib/zena.rb +42 -9
  201. data/lib/zena/acts/enrollable.rb +81 -99
  202. data/lib/zena/acts/secure.rb +27 -23
  203. data/lib/zena/acts/secure_node.rb +10 -55
  204. data/lib/zena/acts/serializable.rb +9 -10
  205. data/lib/zena/app.rb +0 -2
  206. data/lib/zena/code_syntax.rb +1 -1
  207. data/lib/zena/controller/test_case.rb +0 -5
  208. data/lib/zena/core_ext/string.rb +48 -20
  209. data/lib/zena/db.rb +10 -442
  210. data/lib/zena/db_helper/abstract_db.rb +184 -0
  211. data/lib/zena/db_helper/mysql.rb +150 -0
  212. data/lib/zena/db_helper/postgresql.rb +79 -0
  213. data/lib/zena/db_helper/sqlite3.rb +135 -0
  214. data/lib/zena/deploy.rb +4 -1
  215. data/lib/zena/deploy/httpd.rhtml +3 -3
  216. data/lib/zena/deploy/vhost.rhtml +1 -1
  217. data/lib/zena/foxy_parser.rb +37 -18
  218. data/lib/zena/info.rb +3 -13
  219. data/lib/zena/migrator.rb +0 -1
  220. data/lib/zena/parser/zafu_rules.rb +9 -4
  221. data/lib/zena/parser/zazen_rules.rb +5 -5
  222. data/lib/zena/parser/zena_rules.rb +1 -1
  223. data/lib/zena/remote/interface.rb +1 -1
  224. data/lib/zena/site_worker.rb +3 -3
  225. data/lib/zena/test_controller.rb +10 -10
  226. data/lib/zena/use/action.rb +66 -6
  227. data/lib/zena/use/ajax.rb +39 -13
  228. data/lib/zena/use/ancestry.rb +210 -0
  229. data/lib/zena/use/authlogic.rb +30 -1
  230. data/lib/zena/use/calendar.rb +158 -0
  231. data/lib/zena/use/conditional.rb +3 -2
  232. data/lib/zena/use/context.rb +42 -12
  233. data/lib/zena/use/dates.rb +15 -14
  234. data/lib/zena/use/display.rb +54 -7
  235. data/lib/zena/use/error_rendering.rb +1 -0
  236. data/lib/zena/use/field_index.rb +20 -0
  237. data/lib/zena/use/fixtures.rb +12 -9
  238. data/lib/zena/use/forms.rb +230 -106
  239. data/lib/zena/use/fulltext.rb +28 -14
  240. data/lib/zena/use/html_tags.rb +1 -24
  241. data/lib/zena/use/i18n.rb +69 -14
  242. data/lib/zena/use/kpath.rb +60 -0
  243. data/lib/zena/use/ml_index.rb +6 -4
  244. data/lib/zena/use/node_context.rb +63 -0
  245. data/lib/zena/use/prop_eval.rb +90 -0
  246. data/lib/zena/use/query_builder.rb +159 -29
  247. data/lib/zena/use/query_comment.rb +1 -1
  248. data/lib/zena/use/query_node.rb +147 -56
  249. data/lib/zena/use/recursion.rb +2 -2
  250. data/lib/zena/use/relations.rb +31 -19
  251. data/lib/zena/use/rendering.rb +111 -121
  252. data/lib/zena/use/scope_index.rb +230 -0
  253. data/lib/zena/use/search.rb +7 -7
  254. data/lib/zena/use/urls.rb +87 -25
  255. data/lib/zena/use/version_hash.rb +113 -113
  256. data/lib/zena/use/workflow.rb +5 -1
  257. data/lib/zena/use/zafu_attributes.rb +11 -14
  258. data/lib/zena/use/zafu_eval.rb +1 -1
  259. data/lib/zena/use/zafu_safe_definitions.rb +91 -9
  260. data/lib/zena/use/zafu_templates.rb +146 -102
  261. data/lib/zena/use/zazen.rb +5 -4
  262. data/lib/zena/zafu_compiler.rb +1 -0
  263. data/locale/en/LC_MESSAGES/zena.mo +0 -0
  264. data/locale/en/zena.po +0 -1
  265. data/locale/fr/LC_MESSAGES/zena.mo +0 -0
  266. data/locale/fr/zena.mo +0 -0
  267. data/locale/fr/zena.po +4 -4
  268. data/misc/zena +35 -0
  269. data/misc/zena_init +41 -0
  270. data/public/images/ext/{basecontact.png → contact.png} +0 -0
  271. data/public/javascripts/zena.js +35 -7
  272. data/public/stylesheets/admin.css +5 -2
  273. data/public/stylesheets/default.css +2 -1
  274. data/public/stylesheets/popup.css +1 -1
  275. data/public/stylesheets/zena.css +2 -2
  276. data/test/custom_queries/complex.host.yml +12 -5
  277. data/test/fixtures/files/Node-test.zafu +3 -3
  278. data/test/fixtures/files/translations_fr.yml +4 -2
  279. data/test/functional/documents_controller_test.rb +31 -0
  280. data/test/functional/nodes_controller_commit_test.rb +1 -5
  281. data/test/functional/nodes_controller_test.rb +92 -12
  282. data/test/functional/user_sessions_controller_test.rb +2 -2
  283. data/test/functional/users_controller_test.rb +31 -29
  284. data/test/functional/versions_controller_test.rb +2 -2
  285. data/test/functional/virtual_classes_controller_test.rb +2 -2
  286. data/test/integration/multiple_hosts_test.rb +19 -8
  287. data/test/integration/navigation_test.rb +91 -12
  288. data/test/integration/query_node/basic.yml +40 -37
  289. data/test/integration/query_node/complex.yml +23 -18
  290. data/test/integration/query_node/dates.yml +3 -3
  291. data/test/integration/query_node/errors.yml +7 -1
  292. data/test/integration/query_node/filters.yml +41 -35
  293. data/test/integration/query_node/idx_fields.yml +11 -0
  294. data/test/integration/query_node/idx_key_value.yml +77 -0
  295. data/test/integration/query_node/idx_scope.yml +33 -0
  296. data/test/integration/query_node/relations.yml +13 -13
  297. data/test/integration/query_node_test.rb +6 -10
  298. data/test/integration/zafu_compiler/action.yml +19 -6
  299. data/test/integration/zafu_compiler/ajax.yml +111 -51
  300. data/test/integration/zafu_compiler/apphelper.yml +1 -1
  301. data/test/integration/zafu_compiler/asset.yml +1 -1
  302. data/test/integration/zafu_compiler/basic.yml +42 -52
  303. data/test/integration/zafu_compiler/calendar.yml +3 -3
  304. data/test/integration/zafu_compiler/complex.yml +16 -16
  305. data/test/integration/zafu_compiler/complex_ok.yml +2 -2
  306. data/test/integration/zafu_compiler/conditional.yml +42 -33
  307. data/test/integration/zafu_compiler/data.yml +3 -3
  308. data/test/integration/zafu_compiler/dates.yml +25 -10
  309. data/test/integration/zafu_compiler/display.yml +49 -12
  310. data/test/integration/zafu_compiler/errors.yml +26 -6
  311. data/test/integration/zafu_compiler/eval.yml +4 -4
  312. data/test/integration/zafu_compiler/forms.yml +89 -15
  313. data/test/integration/zafu_compiler/i18n.yml +23 -18
  314. data/test/integration/zafu_compiler/idx_scope.yml +7 -0
  315. data/test/integration/zafu_compiler/later.yml +10 -16
  316. data/test/integration/zafu_compiler/off/off.yml +2 -2
  317. data/test/integration/zafu_compiler/query.yml +207 -0
  318. data/test/integration/zafu_compiler/recursion.yml +2 -2
  319. data/test/integration/zafu_compiler/relations.yml +144 -168
  320. data/test/integration/zafu_compiler/roles.yml +86 -10
  321. data/test/integration/zafu_compiler/rubyless.yml +49 -6
  322. data/test/integration/zafu_compiler/safe_definitions.yml +35 -6
  323. data/test/integration/zafu_compiler/search.yml +1 -1
  324. data/test/integration/zafu_compiler/security.yml +37 -0
  325. data/test/integration/zafu_compiler/urls.yml +50 -40
  326. data/test/integration/zafu_compiler/user.yml +21 -6
  327. data/test/integration/zafu_compiler/version.yml +6 -6
  328. data/test/integration/zafu_compiler/zafu_attributes.yml +43 -34
  329. data/test/integration/zafu_compiler/zazen.yml +10 -10
  330. data/test/integration/zafu_compiler_test.rb +19 -13
  331. data/test/sites/complex/nodes.yml +0 -2
  332. data/test/sites/complex/roles.yml +9 -1
  333. data/test/sites/complex/sites.yml +0 -1
  334. data/test/sites/complex/users.yml +2 -5
  335. data/test/sites/ocean/nodes.yml +2 -5
  336. data/test/sites/ocean/roles.yml +8 -0
  337. data/test/sites/ocean/sites.yml +0 -1
  338. data/test/sites/ocean/users.yml +0 -13
  339. data/test/sites/zena/columns.yml +27 -5
  340. data/test/sites/zena/idx_projects.yml +5 -0
  341. data/test/sites/zena/nodes.yml +8 -32
  342. data/test/sites/zena/relations.yml +5 -0
  343. data/test/sites/zena/roles.yml +25 -3
  344. data/test/sites/zena/sites.yml +2 -2
  345. data/test/sites/zena/users.yml +1 -21
  346. data/test/sites/zena/versions.yml +35 -12
  347. data/test/test_helper.rb +7 -0
  348. data/test/unit/after_commit_test.rb +7 -7
  349. data/test/unit/cache_test.rb +32 -0
  350. data/test/unit/cached_page_test.rb +1 -1
  351. data/test/unit/column_test.rb +31 -7
  352. data/test/unit/comment_test.rb +2 -2
  353. data/test/unit/core_ext_test.rb +38 -7
  354. data/test/unit/document_test.rb +14 -42
  355. data/test/unit/node_test.rb +311 -324
  356. data/test/unit/note_test.rb +23 -31
  357. data/test/unit/page_test.rb +16 -58
  358. data/test/unit/project_test.rb +2 -2
  359. data/test/unit/relation_proxy_test.rb +148 -21
  360. data/test/unit/relation_test.rb +23 -3
  361. data/test/unit/remote_test.rb +15 -9
  362. data/test/unit/role_test.rb +9 -0
  363. data/test/unit/site_test.rb +49 -47
  364. data/test/unit/skin_test.rb +16 -0
  365. data/test/unit/template_test.rb +60 -69
  366. data/test/unit/text_document_test.rb +15 -14
  367. data/test/unit/user_test.rb +101 -41
  368. data/test/unit/version_test.rb +4 -4
  369. data/test/unit/virtual_class_test.rb +577 -36
  370. data/test/unit/workflow_test.rb +58 -21
  371. data/test/unit/zena/acts/enrollable_test.rb +36 -127
  372. data/test/unit/zena/acts/secure_test.rb +6 -22
  373. data/test/unit/zena/acts/serializable_test.rb +18 -0
  374. data/test/unit/zena/db_test.rb +14 -14
  375. data/test/unit/zena/parser/zafu.yml +5 -3
  376. data/test/unit/zena/use/ancestry_test.rb +198 -0
  377. data/test/unit/zena/use/calendar_test.rb +8 -8
  378. data/test/unit/zena/use/dates_test.rb +2 -0
  379. data/test/unit/zena/use/fulltext_test.rb +9 -1
  380. data/test/unit/zena/use/html_tags_test.rb +2 -16
  381. data/test/unit/zena/use/i18n_test.rb +2 -2
  382. data/test/unit/zena/use/kpath_test.rb +13 -0
  383. data/test/unit/zena/use/ml_index_test.rb +60 -12
  384. data/test/unit/zena/use/prop_eval_test.rb +170 -0
  385. data/test/unit/zena/use/query_node_test.rb +9 -2
  386. data/test/unit/zena/use/rendering_test.rb +98 -1
  387. data/test/unit/zena/use/scope_index_test.rb +464 -0
  388. data/test/unit/zena/use/urls_test.rb +23 -13
  389. data/test/unit/zena/use/version_hash_test.rb +2 -2
  390. data/test/unit/zena/use/zafu_template_test.rb +21 -8
  391. data/test/unit/zena/use/zazen_test.rb +47 -47
  392. data/zena.gemspec +177 -143
  393. metadata +222 -141
  394. data/app/models/base_contact.rb +0 -79
  395. data/app/models/book.rb +0 -242
  396. data/app/models/contact_content.rb +0 -70
  397. data/app/models/contact_version.rb +0 -40
  398. data/app/models/reference.rb +0 -18
  399. data/app/views/templates/edit_tabs/_basecontact.rhtml +0 -8
  400. data/bricks/data2pdf/README +0 -19
  401. data/bricks/data2pdf/VERSION +0 -1
  402. data/bricks/data2pdf/lib/data2pdf.rb +0 -60
  403. data/bricks/data2pdf/lib/engines/prince.rb +0 -39
  404. data/bricks/data2pdf/lib/engines/xhtml2pdf.rb +0 -41
  405. data/bricks/data2pdf/lib/install.rb +0 -111
  406. data/bricks/data2pdf/test/engines/test_prince.rb +0 -14
  407. data/bricks/data2pdf/test/engines/test_xhtml2pdf.rb +0 -14
  408. data/bricks/data2pdf/test/shoulda_macros/shoulda_data2pdf.rb +0 -91
  409. data/bricks/data2pdf/test/unit/test_rendering.rb +0 -37
  410. data/config/routes.rb +0 -3
  411. data/db/migrate/010_create_template_content.rb +0 -17
  412. data/db/migrate/024_correct_vclass_kpath.rb +0 -13
  413. data/db/migrate/025_move_tag_into_vclass.rb +0 -15
  414. data/lib/version_off.rb +0 -323
  415. data/lib/zena/use/node_name.rb +0 -94
  416. data/test/integration/query_node/properties.yml +0 -41
  417. data/test/unit/base_contact_test.rb +0 -242
  418. data/test/unit/node_name_test.rb +0 -137
@@ -6,11 +6,11 @@ Definitions:
6
6
 
7
7
  Render ---> Master template --include--> helper template --include--> ...
8
8
 
9
- For master templates, the node_name is build from the different filters (target_klass, mode, format):
9
+ For master templates, the title is build from the different filters (target_klass, mode, format):
10
10
 
11
11
  Klass-mode-format. Examples: Node-index, Node--xml, Project-info. Note how the format is omitted when it is 'html'.
12
12
 
13
- Other templates have a node_name built from the given name, just like any other node.
13
+ Other templates have a title built from the given name, just like any other node.
14
14
 
15
15
  =end
16
16
  class Template < TextDocument
@@ -29,7 +29,7 @@ class Template < TextDocument
29
29
  'skin_id' => record[:section_id],
30
30
  }
31
31
  end
32
-
32
+
33
33
  safe_property :tkpath, :mode, :target_klass, :format
34
34
  end
35
35
 
@@ -63,14 +63,14 @@ class Template < TextDocument
63
63
  # Force template content-type to 'text/zafu'
64
64
  self.content_type = 'text/zafu'
65
65
 
66
- if node_name_changed?
67
- if node_name =~ /^([A-Z][a-zA-Z]+?)(-(([a-zA-Z_\+]*)(-([a-zA-Z_]+)|))|)(\.|\Z)/
68
- # node_name/title changed force update
66
+ if prop.title_changed?
67
+ if title =~ /^([A-Z][a-zA-Z]+?)(-(([a-zA-Z_\+]*)(-([a-zA-Z_]+)|))|)(\.|\Z)/
68
+ # title changed force update
69
69
  prop['target_klass'] = $1 unless prop.target_klass_changed?
70
70
  prop['mode'] = ($4 || '').url_name unless prop.mode_changed?
71
71
  prop['format'] = ($6 || 'html') unless prop.format_changed?
72
72
  else
73
- # node_name set but it is not a master template name
73
+ # title set but it is not a master template name
74
74
  prop['target_klass'] = nil
75
75
  prop['mode'] = nil
76
76
  prop['format'] = nil
@@ -78,13 +78,12 @@ class Template < TextDocument
78
78
  end
79
79
 
80
80
  if version.edited?
81
- prop['mode'] = prop['mode'].url_name if prop['mode']
81
+ prop['mode'] = prop['mode'].gsub(/[^a-zA-Z]/, '') if prop['mode']
82
82
 
83
83
  if !prop['target_klass'].blank?
84
- # update node_name
84
+ # update title
85
85
  prop['format'] = 'html' if prop['format'].blank?
86
- self.node_name = node_name_from_mode_and_format
87
- self.title = self.node_name
86
+ self.title = title_from_mode_and_format
88
87
 
89
88
  if text.blank? && prop['format'] == 'html' && prop['mode'] != '+edit'
90
89
  # set a default text
@@ -121,7 +120,7 @@ END_TXT
121
120
  end
122
121
  end
123
122
 
124
- def node_name_from_mode_and_format(opts={})
123
+ def title_from_mode_and_format(opts={})
125
124
  opts[:format] ||= prop['format']
126
125
  opts[:mode ] ||= prop['mode']
127
126
  opts[:target_klass ] ||= prop['target_klass']
@@ -43,8 +43,6 @@ class TextDocument < Document
43
43
  return text
44
44
  end
45
45
 
46
- base_path = parent.fullpath
47
-
48
46
  res.gsub!(/url\(\s*(.*?)\s*\)/) do
49
47
  match, src = $&, $1
50
48
  if src =~ /('|")(.*?)\1/
@@ -71,14 +69,14 @@ class TextDocument < Document
71
69
  else
72
70
  if new_src = helper.send(:template_url_for_asset,
73
71
  :src => src,
74
- :base_path => base_path,
72
+ :parent => parent,
75
73
  :parse_assets => true )
76
74
 
77
75
  "url(#{quote}#{new_src}#{quote})"
78
76
  elsif !(src =~ /\.\./) && File.exist?(File.join(SITES_ROOT, current_site.public_path, src))
79
77
  "url(#{quote}#{src}?#{File.mtime(File.join(SITES_ROOT, current_site.public_path, src)).to_i}#{quote})"
80
78
  else
81
- errors.add('asset', '{{asset}} not found', :asset => src.inspect)
79
+ errors.add('asset', _('%{asset} not found') % {:asset => src.inspect})
82
80
  "url(#{quote}#{src}#{quote})"
83
81
  end
84
82
  end
@@ -129,12 +127,13 @@ class TextDocument < Document
129
127
  zip, mode = $1, $2
130
128
  if asset = secure(Node) { Node.find_by_zip(zip) }
131
129
  if asset.fullpath =~ /\A#{base_path}\/(.+)/
132
- "url(#{quote}#{$1}#{mode}.#{asset.prop['ext']}#{quote})"
130
+ path = fullpath_as_title($1)
131
+ "url(#{quote}#{path}#{mode}.#{asset.prop['ext']}#{quote})"
133
132
  else
134
- "url(#{quote}/#{asset.fullpath}#{mode}.#{asset.prop['ext']}#{quote})"
133
+ "url(#{quote}/#{asset.fullpath_as_title.map(&:to_filename).join('/')}#{mode}.#{asset.prop['ext']}#{quote})"
135
134
  end
136
135
  else
137
- errors.add('asset', '{{zip}} not found', :zip => zip)
136
+ errors.add('asset', '%{zip} not found', :zip => zip)
138
137
  "url(#{quote}#{url}#{quote})"
139
138
  end
140
139
  elsif File.exist?(File.join(SITES_ROOT, current_site.public_path, url.sub(/\?\d+\Z/,'')))
data/app/models/user.rb CHANGED
@@ -45,23 +45,36 @@ class User < ActiveRecord::Base
45
45
  c.validate_password_field = false
46
46
  end
47
47
 
48
+ # Dynamic resolution of the author class from the prototype
49
+ def self.node_user_proc
50
+ Proc.new do |h, r, s|
51
+ res = {:method => 'node', :nil => true}
52
+ if prototype = visitor.prototype
53
+ res[:class] = prototype.vclass
54
+ else
55
+ res[:class] = VirtualClass['Node']
56
+ end
57
+ res
58
+ end
59
+ end
60
+
48
61
  include RubyLess
49
62
 
50
- safe_attribute :login, :name, :first_name, :email, :time_zone, :created_at, :updated_at, :lang, :id
51
- safe_method :initials => String, :fullname => String, :status => Number, :status_name => String,
63
+ safe_attribute :login, :time_zone, :created_at, :updated_at, :lang, :id
64
+ safe_method :initials => String, :status => Number, :status_name => String,
52
65
  :is_anon? => Boolean, :is_admin? => Boolean, :user? => Boolean, :commentator? => Boolean,
53
66
  :moderated? => Boolean
54
67
 
55
- safe_context :contact => 'BaseContact', :node => {:method => 'contact', :class => 'BaseContact'},
68
+ safe_context :node => node_user_proc,
56
69
  :to_publish => ['Version'], :redactions => ['Version'], :proposed => ['Version'],
57
70
  :comments_to_publish => ['Comment']
58
71
 
59
- attr_accessible :login, :lang, :first_name, :name, :email, :time_zone, :status, :group_ids, :site_ids, :crypted_password, :password
72
+ attr_accessible :login, :lang, :node, :time_zone, :status, :group_ids, :site_ids, :crypted_password, :password, :dev_skin_id
60
73
  attr_accessor :visited_node_ids
61
74
  attr_accessor :ip
62
75
 
63
76
  belongs_to :site
64
- belongs_to :contact, :dependent => :destroy, :class_name => 'BaseContact'
77
+ belongs_to :node, :dependent => :destroy # Do we want this ? (won't work if there are sub-nodes...)
65
78
  has_and_belongs_to_many :groups
66
79
  has_many :nodes
67
80
  has_many :versions
@@ -69,11 +82,12 @@ class User < ActiveRecord::Base
69
82
  before_validation :user_before_validation
70
83
  validate :valid_groups
71
84
  validate :valid_user
85
+ validate :valid_node
72
86
  validates_uniqueness_of :login, :scope => :site_id
73
87
 
74
88
  before_destroy :dont_destroy_protected_users
75
89
  validates_presence_of :site_id
76
- before_create :create_contact
90
+ before_create :create_node
77
91
 
78
92
  Status = {
79
93
  :su => 80,
@@ -88,6 +102,8 @@ class User < ActiveRecord::Base
88
102
 
89
103
 
90
104
  class << self
105
+ # This method is used by authlogic and is only called from withing a Secure scope that
106
+ # enforces the proper site_id.
91
107
  def find_allowed_user_by_login(login)
92
108
  first(:conditions=>["login = ? and status > 0", login])
93
109
  end
@@ -110,10 +126,10 @@ class User < ActiveRecord::Base
110
126
 
111
127
  end
112
128
 
113
- def contact_with_secure
114
- @contact ||= secure(BaseContact) { contact_without_secure }
129
+ def node_with_secure
130
+ @node ||= secure(Node) { node_without_secure }
115
131
  end
116
- alias_method_chain :contact, :secure
132
+ alias_method_chain :node, :secure
117
133
 
118
134
 
119
135
  # Each time a node is found using secure (Zena::Acts::Secure or Zena::Acts::SecureNode), this method is
@@ -131,16 +147,51 @@ class User < ActiveRecord::Base
131
147
  @visited_node_ids ||= []
132
148
  end
133
149
 
134
- def fullname
135
- (first_name ? (first_name + " ") : '') + name.to_s
150
+ # Return the prototype user (used by Zafu and QueryBuilder to know the class
151
+ # of the visitor and during user creation)
152
+ def prototype
153
+ @prototype ||= begin
154
+ secure(Node) { Node.new_node(prototype_attributes) }
155
+ end
136
156
  end
137
157
 
138
- def initials
139
- fullname.split(" ").map {|w| w[0..0].capitalize}.join("")
158
+ # Return a new Node to be used by a new User as base for creating the visitor Node.
159
+ def prototype_attributes
160
+ @prototype_attributes ||= begin
161
+ attrs = begin
162
+ if code = current_site.prop['usr_prototype_attributes']
163
+ hash = safe_eval(code)
164
+ if hash.kind_of?(Hash)
165
+ hash
166
+ else
167
+ {}
168
+ end
169
+ else
170
+ {}
171
+ end
172
+ rescue RubyLess::Error => err
173
+ {}
174
+ end
175
+
176
+ # This is a security feature to avoid :_parent_id set manually in usr_prototype_attributes.
177
+ attrs.stringify_keys!
178
+ unless attrs['parent_id']
179
+ attrs[:_parent_id] = current_site[:root_id]
180
+ end
181
+
182
+ attrs['klass'] ||= attrs.delete('class') || 'Node'
183
+
184
+ attrs
185
+ end
140
186
  end
141
187
 
142
- def email
143
- self[:email] || ""
188
+ def node_attributes=(node_attrs)
189
+ if self[:node_id]
190
+ @node = secure!(Node) { node_without_secure }
191
+ else
192
+ @node = secure(Node) { Node.new_node(prototype_attributes) }
193
+ end
194
+ @node.attributes = node_attrs || {}
144
195
  end
145
196
 
146
197
  def status_name
@@ -149,7 +200,7 @@ class User < ActiveRecord::Base
149
200
 
150
201
  # Return true if the user is in the admin group or if the user is the super user.
151
202
  def is_admin?
152
- is_su? || status.to_i >= User::Status[:admin]
203
+ status.to_i >= User::Status[:admin]
153
204
  end
154
205
 
155
206
  # Return true if the user is the anonymous user for the current visited site
@@ -158,12 +209,6 @@ class User < ActiveRecord::Base
158
209
  user_site.anon_id == self[:id] && (!new_record? || self[:login].nil?) # (when creating a new site, anon_id == nil)
159
210
  end
160
211
 
161
- # Return true if the user is the super user for the current visited site
162
- def is_su?
163
- # tested in site_test
164
- user_site.su_id == self[:id]
165
- end
166
-
167
212
  # Return true if the user's status is high enough to start editing nodes.
168
213
  def user?
169
214
  status >= User::Status[:user]
@@ -244,13 +289,8 @@ class User < ActiveRecord::Base
244
289
  end
245
290
 
246
291
  def comments_to_publish
247
- if is_su?
248
- # su can view all
249
- secure(Comment) { Comment.find(:all, :conditions => ['status = ?', Zena::Status[:prop]]) }
250
- else
251
- secure(Comment) { Comment.find(:all, :select=>'comments.*, nodes.node_name', :from=>'comments, nodes, discussions',
292
+ secure(Comment) { Comment.find(:all, :select=>'comments.*', :from=>'comments, nodes, discussions',
252
293
  :conditions => ['comments.status = ? AND discussions.node_id = nodes.id AND comments.discussion_id = discussions.id AND nodes.dgroup_id IN (?)', Zena::Status[:prop], visitor.group_ids]) }
253
- end
254
294
  end
255
295
 
256
296
  # List all versions proposed for publication that the user has the right to publish.
@@ -274,31 +314,21 @@ class User < ActiveRecord::Base
274
314
  self.site || visitor.site # site when User is new
275
315
  end
276
316
 
277
- def create_contact
278
- return unless visitor.site[:root_id] # do not try to create a contact if the root node is not created yet
317
+ def create_node
318
+ return unless visitor.site[:root_id] # do not try to create a node if the root node is not created yet
279
319
 
280
- @contact = secure!(BaseContact) { BaseContact.new(
281
- # owner is the user except for anonymous and super user.
282
- :user_id => visitor[:id],
283
- :title => (name.blank? || first_name.blank?) ? login : fullname,
284
- :first_name => first_name,
285
- :name => (name || login),
286
- :email => email,
287
- :v_status => Zena::Status[:pub]
288
- )}
320
+ @node.version.status = Zena::Status[:pub]
289
321
 
290
- @contact[:parent_id] = site[:root_id]
291
-
292
- unless @contact.save
322
+ unless @node.save
293
323
  # What do we do with this error ?
294
- raise Zena::InvalidRecord, "Could not create contact node for user #{self.id} in site #{site_id} (#{@contact.errors.map{|k,v| [k,v]}.join(', ')})"
324
+ raise Zena::InvalidRecord, "Could not create contact node for user #{self.id} in site #{site_id} (#{@node.errors.map{|k,v| [k,v]}.join(', ')})"
295
325
  end
296
326
 
297
- unless @contact.publish_from
298
- raise Zena::InvalidRecord, "Could not publish contact node for user #{user_id} in site #{site_id} (#{@contact.errors.map{|k,v| [k,v]}.join(', ')})"
327
+ unless @node.publish_from
328
+ raise Zena::InvalidRecord, "Could not publish contact node for user #{user_id} in site #{site_id} (#{@node.errors.map{|k,v| [k,v]}.join(', ')})"
299
329
  end
300
330
 
301
- self[:contact_id] = @contact[:id]
331
+ self[:node_id] = @node[:id]
302
332
  end
303
333
 
304
334
  # Set user defaults.
@@ -362,6 +392,25 @@ class User < ActiveRecord::Base
362
392
  end
363
393
  end
364
394
 
395
+ def valid_node
396
+ return unless visitor.site[:root_id] # do not validate node if the root node is not created yet
397
+ return if !new_record? && !@node
398
+ if !@node
399
+ # force creation of node, even if it is a plain copy of the prototype
400
+ self.node_attributes = {'title' => login}
401
+ else
402
+ @node.title ||= login
403
+ end
404
+
405
+ if @node.valid?
406
+ # ok
407
+ else
408
+ @node.errors.each_error do |err, msg|
409
+ errors.add("node[#{err}]", msg)
410
+ end
411
+ end
412
+ end
413
+
365
414
  # Make sure all users are in the _public_ and _site_ groups. Make sure
366
415
  # the user only belongs to groups in the same site.
367
416
  def valid_groups #:doc:
@@ -6,7 +6,7 @@ class Version < ActiveRecord::Base
6
6
  safe_attribute :created_at, :updated_at, :publish_from, :status, :lang
7
7
  safe_method :node => 'Node', :id => {:class => Number, :method => 'number'},
8
8
  :number => Number, :user => 'User',
9
- :author => {:class => 'User', :method => 'user'}
9
+ :author => Node.author_proc
10
10
 
11
11
  # We need to include Property::Base so that we can read the properties that
12
12
  # we store (useful when listing versions or comparing them).
@@ -54,7 +54,7 @@ class Version < ActiveRecord::Base
54
54
  end
55
55
 
56
56
  def author
57
- user.contact
57
+ user.node
58
58
  end
59
59
 
60
60
  def mark_for_destruction
@@ -1,57 +1,292 @@
1
1
  # encoding: utf-8
2
+
3
+ # The virtual class holds type information and other attributes to build indices
4
+ # and computed properties. This class acts as the "schema" for nodes.
5
+ #
6
+ # Since this class also _uses_ Property to store some of it's data, confusion must not
7
+ # be made between the VirtualClass as a schema (containing Node property definitions) and
8
+ # the VirtualClass' own schema.
9
+ #
10
+ # The roles in the vclass contain self, super and all the attached roles like this (roles for
11
+ # the Letter virtual class):
12
+ #
13
+ # [
14
+ # <VirtualClass:'Letter' paper, search_mono, search>,
15
+ # [
16
+ # <VirtualClass:'Note' >,
17
+ # --> <Role:'Note' >,
18
+ # [
19
+ # <VirtualClass:'Node' >,
20
+ # --> <Role:'Node' cached_role_ids, title, text, summary>,
21
+ # <Role:'Original' weight, origin, tz>,
22
+ # <Role:'Task' assigned>
23
+ # ]
24
+ # ]
25
+ # ]
26
+ #
27
+ # Elements marked with '-->' above are the 'schema' roles used by the real classes to store
28
+ # ruby declared properties. Since Zena is multi-site, there is one VirtualClass instance of
29
+ # the real classes for each site: this is why the ruby declarations are not stored in the
30
+ # VirtualClass itself for real classes.
31
+ #
2
32
  class VirtualClass < Role
3
33
  attr_accessor :import_result
4
34
  belongs_to :create_group, :class_name => 'Group', :foreign_key => 'create_group_id'
5
35
  validate :valid_virtual_class
36
+ attr_accessible :create_group_id, :auto_create_discussion
37
+
38
+ include Property::StoredSchema
6
39
  include Zena::Use::Relations::ClassMethods
7
40
  include Zena::Use::Fulltext::VirtualClassMethods
41
+ include Zena::Use::PropEval::VirtualClassMethods
42
+ include Zena::Use::ScopeIndex::VirtualClassMethods
43
+
44
+ safe_method :roles => {:class => ['Role'], :method => 'sorted_roles'}
45
+ safe_method :relations => {:class => ['RelationProxy'], :method => 'all_relations'}
46
+ safe_method [:relations, String] => {:class => ['RelationProxy'], :method => 'filtered_relations'}
47
+ # All columns defined for a VirtualClass (kpath based).
48
+ safe_method :all_columns => {:class => ['Column'], :method => 'safe_columns'}
8
49
 
9
- # Import a hash of virtual class definitions and try to build the virtual classes.
10
- def self.import(data)
11
- data.keys.map do |klass|
12
- build_virtual_class(klass, data)
50
+ class Cache
51
+ def initialize
52
+ clear_cache!
13
53
  end
14
- end
15
54
 
16
- # Build a virtual class from a name and a hash of virtual class definitions. If
17
- # the superclass is in the data hash, it is built first.
18
- def self.build_virtual_class(klass, data)
19
- return data[klass]['result'] if data[klass].has_key?('result')
20
- if virtual_class = Node.get_class(klass)
21
- if virtual_class.superclass.to_s == data[klass]['superclass']
22
- virtual_class.import_result = 'same'
23
- return data[klass]['result'] = virtual_class
55
+ def find_by_id(id)
56
+ clear_cache! if stale?
57
+
58
+ @cache_by_id[id] || load_vclass(:id => id)
59
+ end
60
+
61
+ def find_by_kpath(kpath)
62
+ clear_cache! if stale?
63
+
64
+ @cache_by_kpath[kpath] || load_vclass(:kpath => kpath)
65
+ end
66
+
67
+ def find_by_name(name)
68
+ clear_cache! if stale?
69
+
70
+ @cache_by_name[name] || load_vclass(:name => name)
71
+ end
72
+
73
+ def all_classes(base_kpath = 'N', without_list = nil)
74
+ load_all_classes!
75
+
76
+ filter_on = %r{\A#{base_kpath}}
77
+
78
+ if without_list
79
+ regexp = []
80
+ without_list.split(',').map(&:strip).each do |without|
81
+ if filter_class = VirtualClass[without]
82
+ regexp << "\\A#{filter_class.kpath}"
83
+ end
84
+ end
85
+ unless regexp.empty?
86
+ filter_off = %r{\A#{regexp.join('|')}}
87
+ end
88
+ end
89
+
90
+ @cache_by_kpath.values.select do |vclass|
91
+ (vclass.kpath =~ filter_on) &&
92
+ (filter_off.nil? || !(vclass.kpath =~ filter_off))
93
+ end.sort {|a, b| a.kpath <=> b.kpath}
94
+ end
95
+
96
+ def clear_cache!
97
+ @updated_at = current_site[:roles_updated_at].to_f
98
+ @cache_by_id = {}
99
+ @cache_by_kpath = {}
100
+ @cache_by_name = {}
101
+ @all_classes_loaded = false
102
+ end
103
+
104
+ def load_all_classes!
105
+ return if @all_classes_loaded
106
+
107
+ conditions = [["site_id = ?"], current_site.id]
108
+ unless @cache_by_id.empty?
109
+ conditions[0] << "id NOT IN (?)"
110
+ conditions << @cache_by_id.keys
111
+ end
112
+
113
+ conditions[0] = conditions[0].join(' AND ')
114
+
115
+ Node.native_classes.each do |kpath, real_class|
116
+ load_roles_and_cache(build_vclass_from_real_class(real_class))
117
+ end
118
+
119
+ VirtualClass.all(
120
+ :conditions => conditions,
121
+ :order => 'kpath ASC').each do |vclass|
122
+ load_roles_and_cache(vclass)
123
+ end
124
+
125
+ @all_classes_loaded = true
126
+ end
127
+
128
+ def stale?
129
+ @updated_at < current_site[:roles_updated_at].to_f
130
+ end
131
+
132
+ def load_vclass(conditions)
133
+ if kpath = conditions[:kpath]
134
+ real_class = Node.native_classes[kpath]
135
+ elsif name = conditions[:name]
136
+ raise if name.kind_of?(Fixnum)
137
+ real_class = Node.native_classes_by_name[name]
138
+ end
139
+
140
+ if real_class
141
+ vclass = build_vclass_from_real_class(real_class)
24
142
  else
25
- virtual_class.errors.add(:base, 'conflict')
26
- return data[klass]['result'] = virtual_class
143
+ vclass = VirtualClass.first(:conditions => conditions.merge(:site_id => current_site.id))
27
144
  end
28
- else
29
- superclass_name = data[klass]['superclass']
30
- if data[superclass_name]
31
- superclass = build_virtual_class(superclass_name, data)
32
- unless superclass.errors.empty?
33
- virtual_class = VirtualClass.new(:name => klass, :superclass => superclass_name, :create_group_id => current_site.public_group_id)
34
- virtual_class.errors.add(:base, 'conflict in superclass')
145
+
146
+ load_roles_and_cache(vclass) if vclass
147
+
148
+ vclass
149
+ end
150
+
151
+ def build_vclass_from_real_class(real_class)
152
+ vclass = VirtualClass.new(:name => real_class.name)
153
+ vclass.kpath = real_class.kpath
154
+ vclass.real_class = real_class
155
+ vclass.include_role real_class.schema
156
+ vclass.instance_variable_set(:@is_real_class, true)
157
+ vclass
158
+ end
159
+
160
+ def load_roles_and_cache(vclass)
161
+ vclass.load_attached_roles!
162
+ @cache_by_id[vclass.id] = vclass if vclass.id
163
+ @cache_by_kpath[vclass.kpath] = vclass
164
+ @cache_by_name[vclass.name] = vclass
165
+ end
166
+ end # Cache
167
+
168
+ class << self
169
+ attr_accessor :caches_by_site
170
+
171
+ # Import a hash of virtual class definitions and try to build the virtual classes.
172
+ def import(data)
173
+ data.keys.map do |klass|
174
+ build_virtual_class(klass, data)
175
+ end
176
+ end
177
+
178
+ def [](name)
179
+ find_by_name(name)
180
+ end
181
+
182
+ def find_by_id(id)
183
+ (self.caches_by_site[current_site.id] ||= Cache.new).find_by_id(id)
184
+ end
185
+
186
+ def find_by_kpath(kpath)
187
+ (self.caches_by_site[current_site.id] ||= Cache.new).find_by_kpath(kpath)
188
+ end
189
+
190
+ def find_by_name(name)
191
+ (self.caches_by_site[current_site.id] ||= Cache.new).find_by_name(name)
192
+ end
193
+
194
+ def expire_cache!
195
+ Zena::Db.set_attribute(current_site, 'roles_updated_at', Time.now.utc)
196
+ self.caches_by_site[current_site.id] = Cache.new
197
+ end
198
+
199
+ def all_classes(base_kpath = 'N', without_list = nil)
200
+ (self.caches_by_site[current_site.id] ||= Cache.new).all_classes(base_kpath, without_list)
201
+ end
202
+
203
+ # Build a virtual class from a name and a hash of virtual class definitions. If
204
+ # the superclass is in the data hash, it is built first.
205
+ def build_virtual_class(klass, data)
206
+ return data[klass]['result'] if data[klass].has_key?('result')
207
+ if virtual_class = VirtualClass[klass]
208
+ virtual_class = virtual_class.dup
209
+ if virtual_class.superclass.to_s == data[klass]['superclass']
210
+ virtual_class.import_result = 'same'
211
+ return data[klass]['result'] = virtual_class
212
+ else
213
+ virtual_class.errors.add(:base, 'conflict')
35
214
  return data[klass]['result'] = virtual_class
36
215
  end
37
- elsif superclass = Node.get_class(superclass_name)
38
- # ok
39
216
  else
40
- virtual_class = VirtualClass.new(:name => klass, :superclass => superclass_name, :create_group_id => current_site.public_group_id)
41
- virtual_class.errors.add(:base, 'missing superclass')
217
+ superclass_name = data[klass]['superclass']
218
+ if data[superclass_name]
219
+ superclass = build_virtual_class(superclass_name, data)
220
+ unless superclass.errors.empty?
221
+ virtual_class = VirtualClass.new(:name => klass, :superclass => superclass_name, :create_group_id => current_site.public_group_id)
222
+ virtual_class.errors.add(:base, 'conflict in superclass')
223
+ return data[klass]['result'] = virtual_class
224
+ end
225
+ elsif superclass = Node.get_class(superclass_name)
226
+ # ok
227
+ else
228
+ virtual_class = VirtualClass.new(:name => klass, :superclass => superclass_name, :create_group_id => current_site.public_group_id)
229
+ virtual_class.errors.add(:base, 'missing superclass')
230
+ return data[klass]['result'] = virtual_class
231
+ end
232
+
233
+ # build
234
+ create_group_id = superclass.id ? superclass.create_group_id : current_site.public_group_id
235
+ virtual_class = create(data[klass].merge(:name => klass, :create_group_id => create_group_id))
236
+ virtual_class.import_result = 'new'
42
237
  return data[klass]['result'] = virtual_class
43
238
  end
239
+ end
240
+
241
+ def export
242
+ # TODO
243
+ end
244
+ end
44
245
 
45
- # build
46
- create_group_id = superclass.kind_of?(VirtualClass) ? superclass.create_group_id : current_site.public_group_id
47
- virtual_class = create(data[klass].merge(:name => klass, :create_group_id => create_group_id))
48
- virtual_class.import_result = 'new'
49
- return data[klass]['result'] = virtual_class
246
+ self.caches_by_site ||= {}
247
+
248
+ # FIXME: how to make sure all sub-classes of Node are loaded before this is called ?
249
+ # TODO: move into helper
250
+ def classes_for_form(opts={})
251
+ group_ids = visitor.group_ids
252
+ if klass = opts.delete(:class)
253
+ if klass = VirtualClass[klass]
254
+ base_kpath = klass.kpath
255
+ else
256
+ base_kpath = self.kpath
257
+ end
258
+ else
259
+ base_kpath = self.kpath
50
260
  end
261
+
262
+ kpath_len = base_kpath.size
263
+
264
+ VirtualClass.all_classes(base_kpath, opts[:without]).map do |vclass|
265
+ if vclass.create_group_id.nil? || group_ids.include?(vclass.create_group_id)
266
+ # white spaces are insecable spaces (not ' ')
267
+ a, b = vclass.kpath, vclass.name
268
+ [('  ' * (a.size - kpath_len)) + b, b]
269
+ else
270
+ nil
271
+ end
272
+ end.compact
51
273
  end
52
274
 
53
- def self.export
54
- # TODO
275
+ # Include all roles into the this schema. By including the superclass
276
+ # and all roles related to this class.
277
+ def load_attached_roles!
278
+ return if @attached_roles_loaded
279
+
280
+ super_kpath = kpath[0..-2]
281
+ if super_kpath != ''
282
+ include_role VirtualClass.find_by_kpath(super_kpath)
283
+ end
284
+
285
+ attached_roles.each do |role|
286
+ include_role role
287
+ end
288
+
289
+ @attached_roles_loaded = true
55
290
  end
56
291
 
57
292
  def to_s
@@ -62,35 +297,137 @@ class VirtualClass < Role
62
297
  self[:icon] = txt.gsub('..', '.') # SECURITY
63
298
  end
64
299
 
65
- # FIXME: how to make sure all sub-classes of Node are loaded before this is called ?
66
- def classes_for_form(opts={})
67
- all_classes(opts).map{|a,b| [a[0..-1].sub(/^#{self.kpath}/,'').gsub(/./,'  ') + b.to_s, b.to_s] } # white spaces are insecable spaces (not ' ')
300
+ # check inheritance chain through kpath
301
+ def kpath_match?(kpath)
302
+ self.kpath =~ /^#{kpath}/
303
+ end
304
+
305
+ # Return true if the class reflects a real class (proxy for Ruby class).
306
+ def real_class?
307
+ @is_real_class
68
308
  end
69
309
 
70
- def all_classes(opts={})
71
- classes = VirtualClass.find(:all, :conditions => ["site_id = ? AND create_group_id IN (?) AND kpath LIKE '#{self.kpath}%'", current_site[:id], visitor.group_ids]).map{|r| [r.kpath, r.name] }.sort{|a,b| a[0] <=> b[0] }
72
- if opts[:without]
73
- reject_kpath = opts[:without].split(',').map(&:strip).map {|name| Node.get_class(name) }.compact.map { |kla| kla.kpath }.join('|')
74
- classes.reject! {|k,c| k =~ /^#{reject_kpath}/ }
310
+ # Proxy methods for real class --------------
311
+
312
+ # We use the VirtualClass to act as a proxy for the real class when resolving
313
+ # RubyLess methods. If the class reflects a 'real' class, only the methods
314
+ # explicitely declared as safe are safe. If the VirtualClass reflects a virtual
315
+ # class, all properties are considered safe.
316
+ def safe_method_type(signature, receiver = nil)
317
+ if signature.size == 1 && (type = safe_column_types[signature.first])
318
+ type
319
+ else
320
+ real_class.safe_method_type(signature, receiver)
75
321
  end
76
- classes
77
322
  end
78
323
 
79
- # check inheritance chain through kpath
80
- def kpath_match?(kpath)
81
- self.kpath =~ /^#{kpath}/
324
+ # Return safe columns including super class's safe columns
325
+ def defined_safe_columns
326
+ @defined_safe_columns ||= if real_class?
327
+ # Get columns from the 'native' schema of the real class (this schema is a Property::Role,
328
+ # not a VirtualClass or ::Role).
329
+ #
330
+ # Only columns explicitly declared safe are safe here
331
+ real_class.schema.defined_columns.values.select do |col|
332
+ real_class.safe_method_type([col.name])
333
+ end.sort {|a,b| a.name <=> b.name}
334
+ else
335
+ super
336
+ end
82
337
  end
83
338
 
339
+ # Return safe columns including super class's safe columns. The columns are
340
+ # sorted by kpath, origin (VirtualClass first, Role next) and name.
341
+ def safe_columns
342
+ @safe_columns ||= begin
343
+ (superclass.kind_of?(VirtualClass) ? superclass.safe_columns : []) +
344
+ defined_safe_columns +
345
+ attached_roles.map(&:defined_safe_columns).flatten.sort {|a,b| a.name <=> b.name}
346
+ end
347
+ end
348
+
349
+ # Returns a hash of all column types that are RubyLess safe (declared as safe in a real class
350
+ # or just dynamic properties declared in the DB). In the Role: everything is safe
351
+ # (see VirtualClass#safe_column_types).
352
+ def safe_column_types
353
+ @safe_column_types ||= Hash[*safe_columns.map do |column|
354
+ [column.name, RubyLess::SafeClass.safe_method_type_for_column(column, true)]
355
+ end.flatten]
356
+ end
357
+
358
+ # List all roles ordered by ascending kpath and name
359
+ def sorted_roles
360
+ @sorted_roles ||= begin
361
+ res = []
362
+ if superclass.kind_of?(VirtualClass)
363
+ res << superclass.sorted_roles
364
+ end
365
+ res << self unless defined_safe_columns.empty?
366
+ attached_roles.sort{|a,b| a.name <=> b.name}.each do |role|
367
+ res << role unless role.defined_safe_columns.empty?
368
+ end
369
+ res.flatten!
370
+ res
371
+ end
372
+ end
373
+
374
+ # Return virtual class' super class or Node for the virtual class of
375
+ # Node.
84
376
  def superclass
85
- if new_record?
377
+ if kpath && kpath.size > 1
378
+ VirtualClass.find_by_kpath(kpath[0..-2])
379
+ else
86
380
  Node
381
+ end
382
+ end
383
+
384
+ # This is used by RubyLess in method signatures: [:zen_path, #<VirtualClass 'Post'>] ---> [:zen_path, Node]
385
+ def ancestors
386
+ @ancestors ||= [real_class] + real_class.ancestors
387
+ end
388
+
389
+ # Test ancestry
390
+ def <=(other_class)
391
+ if other_class.kind_of?(VirtualClass)
392
+ kpath = other_class.kpath
393
+ self.kpath[0..(kpath.length-1)] == kpath
394
+ else
395
+ real_class <= other_class
396
+ end
397
+ end
398
+
399
+ # Test ancestry
400
+ def <(other_class)
401
+ if other_class.kind_of?(VirtualClass)
402
+ kpath = other_class.kpath
403
+ self.kpath != kpath && self.kpath[0..(kpath.length-1)] == kpath
404
+ elsif real_class.kpath != self.kpath
405
+ # Sub class of real_class
406
+ real_class <= other_class
87
407
  else
88
- Node.get_class_from_kpath(kpath[0..-2])
408
+ # VirtualClass of the real_class
409
+ real_class < other_class
89
410
  end
90
411
  end
91
412
 
413
+
414
+ # Return the SQLiss query compiler.
415
+ def query_compiler
416
+ real_class.query_compiler
417
+ end
418
+
419
+ # Build SQLiss query.
420
+ def build_query(*args)
421
+ real_class.build_query(*args)
422
+ end
423
+
424
+ # Execute find
425
+ def do_find(*args)
426
+ real_class.do_find(*args)
427
+ end
428
+
92
429
  def superclass=(klass)
93
- if k = Node.get_class(klass)
430
+ if k = VirtualClass[klass]
94
431
  @superclass = k
95
432
  else
96
433
  errors.add('superclass', 'invalid')
@@ -99,54 +436,63 @@ class VirtualClass < Role
99
436
 
100
437
  # Build new nodes instances of this VirtualClass
101
438
  def new_instance(hash={})
102
- real_class.new(hash.merge(:kpath => self.kpath, :vclass_id => self.id))
439
+ real_class.new(hash, self)
103
440
  end
104
441
 
105
442
  # Create new nodes instances of this VirtualClass
106
443
  def create_instance(*args)
107
- if @scope
108
- # FIXME: remove 'with_exclusive_scope' once scopes are clarified and removed from 'secure'
109
- real_class.send(:with_exclusive_scope, @scope) {
110
- obj = self.new_instance(*args)
111
- obj.save
112
- obj
113
- }
114
- else
115
- obj = self.new_instance(*args)
116
- obj.save
117
- obj
118
- end
444
+ obj = self.new_instance(*args)
445
+ obj.save
446
+ obj
119
447
  end
120
448
 
121
449
  def real_class
122
- klass = Module::const_get(self[:real_class])
123
- raise NameError unless klass.ancestors.include?(Node)
124
- klass
450
+ @real_class ||= begin
451
+ klass = Module::const_get(self[:real_class] || 'Node')
452
+ raise NameError unless klass.ancestors.include?(Node)
453
+ klass
454
+ end
125
455
  end
126
456
 
127
- def with_exclusive_scope(scope, &block)
128
- @scope = scope
129
- res = yield
130
- @scope = nil
131
- res
457
+ def real_class=(klass)
458
+ @real_class = klass
132
459
  end
133
460
 
134
461
  def import_result
135
462
  @import_result || errors[:base]
136
463
  end
137
464
 
465
+ # List all relations that can be set for this class, filtering by
466
+ # relation group.
467
+ def filtered_relations(group_filter)
468
+ all_relations(nil, group_filter)
469
+ end
470
+
471
+ # Cache index groups
472
+ def index_groups
473
+ @index_groups ||= super
474
+ end
475
+
138
476
  private
477
+ def attached_roles
478
+ ::Role.all(
479
+ :conditions => ['kpath = ? AND site_id = ? AND type != ?',
480
+ kpath, current_site.id, 'VirtualClass'],
481
+ :order => 'kpath ASC'
482
+ )
483
+ end
484
+
139
485
  def valid_virtual_class
140
486
  return if !errors.empty?
141
487
  @superclass ||= self.superclass
142
488
 
143
- if new_record? || name_changed? || @superclass != old.superclass
489
+ if real_class? || name_changed? || @superclass != old.superclass
144
490
  index = 0
145
491
  kpath = nil
146
492
  while index < self[:name].length
147
493
  try_kpath = @superclass.kpath + self[:name][index..index].upcase
148
- if found = Node.get_class_from_kpath(try_kpath)
149
- if found.kind_of?(VirtualClass) && found[:id] == self[:id]
494
+ if found = VirtualClass.find_by_kpath(try_kpath)
495
+ if found.id && found.id == self[:id]
150
496
  kpath = try_kpath
151
497
  break
152
498
  end
@@ -169,7 +515,6 @@ class VirtualClass < Role
169
515
  unless self[:real_class]
170
516
  errors.add('superclass', 'invalid')
171
517
  end
172
-
173
518
  end
174
519
 
175
520
  def get_real_class(klass)