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
data/app/models/cache.rb CHANGED
@@ -4,44 +4,49 @@ class Cache < ActiveRecord::Base
4
4
  cattr_accessor :perform_caching
5
5
  before_save :set_site_id
6
6
 
7
+ # The code is here too bad to be kept.
8
+ # TODO: Rewrite
7
9
  class << self
8
10
 
9
11
  def with(visitor_id, visitor_groups, kpath, *context)
10
- return yield unless perform_caching
11
- if cached = self.find(:first, :conditions => ["visitor_id = ? AND site_id = ? AND context = ?", visitor_id, visitor.site.id, context.join('.').hash.abs])
12
- cached[:content]
13
- else
14
- content = yield
15
- self.create(:visitor_id=>visitor_id, :visitor_groups=>".#{visitor_groups.join('.')}.", :kpath=>kpath,
16
- :context=>context.join('.').hash.abs, :content=>content )
17
- content
18
- end
12
+ yield
13
+
14
+ # return yield unless perform_caching
15
+ # if cached = self.find(:first, :conditions => ["visitor_id = ? AND site_id = ? AND context = ?", visitor_id, visitor.site.id, context.join('.').hash.abs])
16
+ # cached[:content]
17
+ # else
18
+ # content = yield
19
+ # self.create(:visitor_id=>visitor_id, :visitor_groups=>".#{visitor_groups.join('.')}.", :kpath=>kpath,
20
+ # :context=>context.join('.').hash.abs, :content=>content )
21
+ # content
22
+ # end
19
23
  end
20
24
 
21
25
  # We can provide a kpath selector for sweeping. If the kpath is in the cached scope, the cache is removed.
22
26
  def sweep(hash)
23
- if kpath = hash[:kpath]
24
- klasses = []
25
- kpath.split(//).each_index { |i| klasses << kpath[0..i] }
26
- kpath_selector = " AND kpath IN (#{klasses.map{|k| connection.quote(k)}.join(',')})"
27
- else
28
- kpath_selector = ""
29
- end
30
- if hash[:visitor_id]
31
- self.connection.execute "DELETE FROM #{self.table_name} WHERE visitor_id = '#{hash[:visitor_id]}'" + kpath_selector
32
- end
33
- if hash[:visitor_groups]
34
- hash[:visitor_groups].each do |g|
35
- self.connection.execute "DELETE FROM #{self.table_name} WHERE visitor_groups LIKE '%.#{g}.%'" + kpath_selector
36
- end
37
- end
38
- if hash[:context]
39
- context = [hash[:context]].flatten.join('.').hash.abs
40
- self.connection.execute "DELETE FROM #{self.table_name} WHERE context = '#{context}'" + kpath_selector
41
- end
42
- if hash[:older_than]
43
- self.connection.execute "DELETE FROM #{self.table_name} WHERE updated_at < '#{hash[:older_than]}'" + kpath_selector
44
- end
27
+ return
28
+ # if kpath = hash[:kpath]
29
+ # klasses = []
30
+ # kpath.split(//).each_index { |i| klasses << kpath[0..i] }
31
+ # kpath_selector = " AND kpath IN (#{klasses.map{|k| connection.quote(k)}.join(',')})"
32
+ # else
33
+ # kpath_selector = ""
34
+ # end
35
+ # if hash[:visitor_id]
36
+ # self.connection.execute "DELETE FROM #{self.table_name} WHERE visitor_id = '#{hash[:visitor_id]}'" + kpath_selector
37
+ # end
38
+ # if hash[:visitor_groups]
39
+ # hash[:visitor_groups].each do |g|
40
+ # self.connection.execute "DELETE FROM #{self.table_name} WHERE visitor_groups LIKE '%.#{g}.%'" + kpath_selector
41
+ # end
42
+ # end
43
+ # if hash[:context]
44
+ # context = [hash[:context]].flatten.join('.').hash.abs
45
+ # self.connection.execute "DELETE FROM #{self.table_name} WHERE context = '#{context}'" + kpath_selector
46
+ # end
47
+ # if hash[:older_than]
48
+ # self.connection.execute "DELETE FROM #{self.table_name} WHERE updated_at < '#{hash[:older_than]}'" + kpath_selector
49
+ # end
45
50
  end
46
51
  end
47
52
 
data/app/models/column.rb CHANGED
@@ -2,8 +2,11 @@ class Column < ActiveRecord::Base
2
2
  attr_accessor :import_result
3
3
  include RubyLess
4
4
  include Property::StoredColumn
5
- TYPES_FOR_FORM = %w{string datetime integer}
6
- INDICES_FOR_FORM = %w{string ml_string}
5
+ TYPES_FOR_FORM = %w{string datetime integer float}
6
+
7
+ INDICES_FOR_FORM = %w{string ml_string datetime integer float}
8
+
9
+ FIELD_INDICES = []
7
10
 
8
11
  belongs_to :role
9
12
  before_validation :set_defaults
@@ -12,14 +15,37 @@ class Column < ActiveRecord::Base
12
15
  validates_uniqueness_of :name, :scope => :site_id
13
16
  validate :name_not_in_models
14
17
 
18
+ after_save :expire_vclass_cache
19
+ after_destroy :expire_vclass_cache
20
+
15
21
  safe_method :name => String
16
22
 
17
23
  class << self
18
24
  include Zena::Acts::Secure
19
25
 
20
26
  def roles_for_form
21
- secure(Role) { Role.all(:order => 'name ASC') }.map do |role|
22
- [role.name, role.id]
27
+ if roles = secure(Role) { Role.all(:order => 'name ASC') }
28
+ roles.map do |role|
29
+ [role.name, role.id]
30
+ end
31
+ else
32
+ []
33
+ end
34
+ end
35
+
36
+ def indices_for_form
37
+ [
38
+ ['key/value',
39
+ INDICES_FOR_FORM.map {|i| [i, i]}],
40
+ ['field',
41
+ FIELD_INDICES.map {|i| [i, ".#{i}"]}]
42
+ ]
43
+ end
44
+
45
+ # Declare a new index table or field
46
+ def add_field_index(*args)
47
+ args.flatten.each do |idx|
48
+ FIELD_INDICES << idx
23
49
  end
24
50
  end
25
51
 
@@ -77,18 +103,35 @@ class Column < ActiveRecord::Base
77
103
  @kpath ||= role.kpath
78
104
  end
79
105
 
106
+ # Used to display index name in forms
107
+ def index_name
108
+ self.index.to_s.gsub(/\A\./,'')
109
+ end
110
+
80
111
  protected
81
112
  def set_defaults
82
113
  self[:site_id] = current_site.id
83
114
  end
84
115
 
85
116
  def name_not_in_models
86
- Node.native_classes.each do |kpath, klass|
117
+ Node.native_classes.to_a.sort{|a,b| a[0] <=> b[0]}.each do |kpath, klass|
118
+ name, set_name = self.name, "#{self.name}="
87
119
  if column = klass.schema.columns[self.name]
88
120
  # find column origin
89
121
  errors.add(:name, _('has already been taken in %s') % column.role.name)
90
122
  break
123
+ elsif self.name =~ %r{_ids?$}
124
+ errors.add(:name, _('invalid (cannot end with _id or _ids)'))
125
+ break
126
+ elsif klass.method_defined?(name) || klass.protected_method_defined?(name) || klass.private_method_defined?(name) ||
127
+ klass.method_defined?(set_name) || klass.protected_method_defined?(set_name) || klass.private_method_defined?(set_name)
128
+ errors.add(:name, _('invalid (method defined in %s)') % klass.to_s)
129
+ break
91
130
  end
92
131
  end
93
132
  end
133
+
134
+ def expire_vclass_cache
135
+ VirtualClass.expire_cache!
136
+ end
94
137
  end
@@ -9,8 +9,11 @@ class Comment < ActiveRecord::Base
9
9
  include RubyLess
10
10
 
11
11
  safe_attribute :title, :created_at, :updated_at, :status
12
- safe_method :text => String, :author_name => {:class => String, :nil => true},
13
- :discussion_id => Number
12
+ safe_method :text => String,
13
+ :discussion_id => Number,
14
+ :user_name => {:class => String, :nil => true},
15
+ :user => {:class => 'User', :nil => true},
16
+ :author => Node.author_proc
14
17
 
15
18
  safe_context :replies => ['Comment'], :node => 'Node'
16
19
 
@@ -23,8 +26,14 @@ class Comment < ActiveRecord::Base
23
26
 
24
27
  include Zena::Use::QueryComment::ModelMethods
25
28
 
29
+ # Who wrote the comment (author's User model)
30
+ def user
31
+ @user ||= secure(User) { User.find(self[:user_id]) }
32
+ end
33
+
34
+ # Who wrote the comment (author's Node model)
26
35
  def author
27
- @author ||= secure(User) { User.find(self[:user_id]) }
36
+ @author ||= (user ? user.node : nil)
28
37
  end
29
38
 
30
39
  def node
@@ -32,7 +41,7 @@ class Comment < ActiveRecord::Base
32
41
  end
33
42
 
34
43
  def author_name
35
- self[:author_name] || (self[:user_id] ? author.fullname : nil)
44
+ self[:author_name] || (author ? author.title : nil)
36
45
  end
37
46
 
38
47
  def parent
@@ -111,7 +120,7 @@ class Comment < ActiveRecord::Base
111
120
  errors.add('text', "can't be blank") if self[:text].blank?
112
121
  errors.add('discussion', 'invalid') unless discussion
113
122
  errors.add('ip', "can't be blank") unless self[:ip] || !visitor.is_anon?
114
- if author.is_anon?
123
+ if user.is_anon?
115
124
  errors.add('author_name', "can't be blank") unless self[:author_name] && self[:author_name] != ""
116
125
  end
117
126
  end
@@ -19,7 +19,7 @@ class DataEntry < ActiveRecord::Base
19
19
  :node_a_zip => Number, :node_b_zip => Number,
20
20
  :node_c_zip => Number, :node_d_zip => Number,
21
21
  :node_a => 'Node', :node_b => 'Node', :node_c => 'Node',
22
- :node_d => 'Node', :nodes => ['Node'], :author => 'BaseContact', :user => 'User'
22
+ :node_d => 'Node', :nodes => ['Node'], :author => 'Node', :user => 'User'
23
23
 
24
24
  attr_protected :site_id
25
25
 
@@ -84,7 +84,7 @@ class DataEntry < ActiveRecord::Base
84
84
  end
85
85
 
86
86
  def author
87
- user.contact
87
+ user.node
88
88
  end
89
89
 
90
90
  def nodes
@@ -46,9 +46,10 @@ class Document < Node
46
46
  safe_property :size, :content_type, :ext
47
47
  safe_method :filename => String, :file => File, :filepath => String
48
48
 
49
- validate :valid_file
50
- validate :valid_content_type
51
- after_save :clear_new_file
49
+ validate :make_unique_title
50
+ validate :valid_file
51
+ validate :valid_content_type
52
+ after_save :clear_new_file
52
53
 
53
54
  class << self
54
55
 
@@ -59,10 +60,7 @@ class Document < Node
59
60
  alias o_new new
60
61
 
61
62
  # Return a new Document or a sub-class of Document depending on the file's content type. Returns a TextDocument if there is no file.
62
- def new(attrs = {})
63
-
64
- scope = self.scoped_methods[0] || {}
65
-
63
+ def new(attrs = {}, vclass = nil)
66
64
  attrs = attrs.stringify_keys
67
65
  file = attrs['file'] || ((attrs['version_attributes'] || {})['content_attributes'] || {})['file']
68
66
  if attrs['content_type']
@@ -71,23 +69,29 @@ class Document < Node
71
69
  content_type = file.content_type
72
70
  elsif ct = attrs['content_type']
73
71
  content_type = ct
74
- elsif attrs['node_name'] =~ /^.*\.(\w+)$/ && types = Zena::EXT_TO_TYPE[$1.downcase]
75
- content_type = types[0]
76
72
  elsif attrs['title'] =~ /^.*\.(\w+)$/ && types = Zena::EXT_TO_TYPE[$1.downcase]
77
73
  content_type = types[0]
78
74
  end
75
+
76
+ real_class = document_class_from_content_type(content_type)
79
77
 
80
- klass = document_class_from_content_type(content_type)
78
+ unless vclass && vclass.kpath =~ /\A#{real_class.kpath}/
79
+ # vclass is not compatible (force kpath)
80
+ vclass = VirtualClass[real_class.to_s]
81
+ end
81
82
 
82
83
  attrs['content_type'] = content_type
83
84
 
84
- if klass != self
85
- klass.with_scope(scope) { klass.o_new(attrs) }
85
+ if real_class != self
86
+ secure(real_class) { real_class.o_new(attrs, vclass) }
86
87
  else
87
- klass.o_new(attrs)
88
+ super(attrs, vclass)
88
89
  end
89
90
  end
90
91
 
92
+ # Compatibility with VirtualClass
93
+ alias new_instance new
94
+
91
95
  # Class list to which this class can change to
92
96
  def change_to_classes_for_form
93
97
  classes_for_form(:class => 'Document', :without => 'Image')
@@ -144,6 +148,7 @@ class Document < Node
144
148
  end
145
149
 
146
150
  # Get the document's public filename using the name and the file extension.
151
+ # FIXME: shouldn't we use title here ?
147
152
  def filename
148
153
  version.attachment.filename
149
154
  end
@@ -153,11 +158,6 @@ class Document < Node
153
158
  version.attachment.filepath(format)
154
159
  end
155
160
 
156
- # Get the node's rootpath with the file's extention.
157
- def rootpath
158
- super + ".#{prop['ext']}"
159
- end
160
-
161
161
  protected
162
162
  def set_defaults
163
163
  set_defaults_from_file
@@ -168,10 +168,6 @@ class Document < Node
168
168
  self.title = $1
169
169
  end
170
170
 
171
- if node_name.to_s =~ /\A(.*)\.#{self.ext}$/i
172
- self.node_name = $1
173
- end
174
-
175
171
  super
176
172
 
177
173
  set_attachment_filename
@@ -224,20 +220,14 @@ class Document < Node
224
220
  return unless @new_file
225
221
  self.content_type = @new_file.content_type unless prop.content_type_changed?
226
222
 
227
- if base = node_name || title || @new_file.original_filename
228
- if base =~ /(.*)\.(\w+)$/
229
- self.node_name = $1 if new_record?
230
- else
231
- self.node_name = base if new_record?
232
- end
223
+ if base = @new_file.original_filename
224
+ self.title = base if title.blank?
233
225
  end
234
226
  end
235
227
 
236
- # Make sure node_name is unique. This should be run after sync_node_name, this is why we
237
- # hack around the name and use super.
238
- def sync_node_name
239
- super
240
- get_unique_node_name_in_scope('ND%')
228
+ # Make sure title is unique. This should be run after prop_eval.
229
+ def make_unique_title
230
+ get_unique_title_in_scope('ND')
241
231
  end
242
232
 
243
233
  def get_extension
@@ -0,0 +1,4 @@
1
+ # This is a dummy class that is only loaded during testing (to load fixtures and
2
+ # to count/find index entries).
3
+ class IdxNodesDatetime < ActiveRecord::Base
4
+ end
@@ -0,0 +1,4 @@
1
+ # This is a dummy class that is only loaded during testing (to load fixtures and
2
+ # to count/find index entries).
3
+ class IdxNodesFloat < ActiveRecord::Base
4
+ end
@@ -0,0 +1,3 @@
1
+ class IdxProject < ActiveRecord::Base
2
+ include Zena::Use::ScopeIndex::IndexMethods
3
+ end # IdxProject
data/app/models/node.rb CHANGED
@@ -25,44 +25,13 @@ Node (manages access and publication cycle)
25
25
  | +--- Template (entry for rendering)
26
26
  |
27
27
  +-- Note (date related information, event)
28
- | |
29
- | +--- Post (blog entry)
30
- | |
31
- | +--- Task
32
- | | |
33
- | | +--- Letter
34
- | | |
35
- | | +--- Request
36
- | | |
37
- | | +--- Bug
38
- | |
39
- | +--- Milestone
40
- |
41
- +-- Reference
42
28
  |
43
- +-- BaseContact (address, name, phone)
44
-
45
- === Node, Version and Content
46
-
47
- The +nodes+ table only holds columns to secure the access. This table does not hold every possible data for every sub-class of Node. The text data is stored into the +versions+ table and any other specific content goes in its own table (+document_contents+ for example). This is an example of how an Image is stored :
29
+ +--- Post (blog entry)
48
30
 
49
- Node o----------- Version o--------- Content
50
- dgroup_id title width
51
- wgroup_id text height
52
- user_id summary content_type
53
- ... ... ...
54
31
 
55
- === Acessing version and content data
32
+ === Properties
56
33
 
57
- To ease the work to set/retrieve the data from the version and or content, we use some special notation. This notation abstracts this Node/Version/Content structure so you can use a version's attribute as if it was in the node directly.
58
-
59
- TODO: DOC removed (was out of sync)
60
-
61
- === Dynamic attributes
62
-
63
- The Version class uses dynamic attributes. These let you add any attribute you like to the versions (see DynAttribute for details). These attributes can be accessed by using the +d_+ prefix :
64
-
65
- @node.d_whatever ===> @node.version.prop[:whatever]
34
+ The Version class stores the node's properties (attributes). You need to declare the attributes either in the virtual class or as a Role attached to an existing class in order to use them.
66
35
 
67
36
  === Attributes
68
37
 
@@ -71,13 +40,15 @@ Each node uses the following basic attributes:
71
40
  Base attributes:
72
41
 
73
42
  zip:: unique id (incremented in each site's scope).
74
- node_name:: used to build the node's url when 'custom_base' is set.
43
+ _id:: cached title (used to identify nodes in DB: not used in Zena)
75
44
  site_id:: site to which this node belongs to.
76
45
  parent_id:: parent node (every node except root is inserted in a unique place through this attribute).
77
- user_id:: owner of the node.
46
+ user_id:: creator of the node.
78
47
  ref_lang:: original node language.
79
48
  created_at:: creation date.
80
49
  updated_at:: modification date.
50
+ log_at:: announcement date.
51
+ event_at:: event date.
81
52
  custom_base:: boolean value. When set to true, the node's url becomes it's fullpath. All it descendants will use this node's fullpath as their base url. See below for an example.
82
53
  inherit:: inheritance mode (0=custom, 1=inherit, -1=private).
83
54
 
@@ -91,11 +62,13 @@ skin_id:: Skin to use when rendering the page ('theme').
91
62
  Attributes used internally:
92
63
  publish_from:: earliest publication date from all published versions.
93
64
  kpath:: inheritance hierarchy. For example an Image has 'NPDI' (Node, Page, Document, Image), a Letter would have 'NNTL' (Node, Note, Task. Letter). This is used to optimize sql queries.
94
- fullpath:: cached full path made of ancestors' node_names (<gdparent node_name>/<parent node_name>/<self node_name>).
65
+ fullpath:: cached full path made of ancestors' zip (<gdparent zip>/<parent zip>/<self zip>).
95
66
  basepath:: cached base path (the base path is used to build the url depending on the 'custom_base' flag).
96
67
 
97
68
  === Node url
69
+
98
70
  A node's url is made of it's class and +zip+. For the examples below, this is our site tree:
71
+
99
72
  root
100
73
  |
101
74
  +--- projects (Page)
@@ -121,11 +94,53 @@ and the 'photos' url is now in the worldTour project's basepath:
121
94
  Setting 'custom_base' on a node should be done with caution as the node's zip is on longer in the url and when you move the node around, there is no way to find the new location from the old url. Custom_base should therefore only be used for nodes that are not going to move.
122
95
  =end
123
96
  class Node < ActiveRecord::Base
97
+ # Only store partial class name in 'type' field (Page instead of ::Page)
98
+ self.store_full_sti_class = false
99
+
124
100
  extend Zena::Use::Upload::UploadedFile
125
101
  extend Zena::Use::Search::NodeClassMethods
126
102
 
127
103
  include Property
128
104
 
105
+ # This must come before the first call to make_schema.
106
+ include Zena::Use::Kpath::InstanceMethods
107
+
108
+ def virtual_class
109
+ @virtual_class ||= if self[:vclass_id]
110
+ VirtualClass.find_by_id(self[:vclass_id])
111
+ else
112
+ VirtualClass.find_by_name(self.class.name)
113
+ end
114
+ end
115
+
116
+ def virtual_class=(vclass)
117
+ @virtual_class = vclass
118
+ self[:vclass_id] = vclass.id
119
+ self[:kpath] = vclass.kpath
120
+ end
121
+
122
+ # We want to use a Role as schema for properties defined in the real_class instead of Property::Schema.
123
+ def self.make_schema
124
+ ::Role.new(:name => name).tap do |role|
125
+ role.kpath = self.kpath
126
+ # Enable property method definitions.
127
+ role.klass = self
128
+ # Used for property inheritance.
129
+ role.real_class = self
130
+ end
131
+ end
132
+
133
+ alias schema virtual_class
134
+
135
+ # We use the virtual_class as proxy for method type resolution.
136
+ # def safe_eval(code)
137
+ # eval RubyLess.translate(schema, code)
138
+ # end
139
+
140
+ def safe_method_type(signature, receiver = nil)
141
+ schema.safe_method_type(signature, receiver)
142
+ end
143
+
129
144
  # Should be the same serialization as in Version and Site
130
145
  include Property::Serialization::JSON
131
146
 
@@ -133,37 +148,19 @@ class Node < ActiveRecord::Base
133
148
 
134
149
  property do |p|
135
150
  # Multilingual string index on 'title'
136
- p.string 'title', :index => Proc.new {|r| {'title' => r.version.idx_text_high}}, :index_group => :ml_string
151
+ p.string 'title', :index => :ml_string
137
152
 
138
153
  p.string 'text'
139
154
  p.string 'summary'
140
- p.string 'comment'
141
155
  end
142
156
 
143
157
  # This is used to enable multilingual indexes
144
158
  include Zena::Use::MLIndex::ModelMethods
145
159
 
146
- include RubyLess
147
- safe_property :title, :text, :summary, :comment
148
-
149
- safe_attribute :created_at, :updated_at, :event_at, :log_at, :publish_from, :basepath, :inherit, :position
150
-
151
- # we use safe_method because the columns can be null, but the values are never null
152
- safe_method :node_name => String, :kpath => String, :user_zip => Number, :parent_zip => Number,
153
- :project_zip => Number, :section_zip => Number, :skin => String, :ref_lang => String,
154
- :fullpath => String, :rootpath => String, :position => Number, :rgroup_id => Number,
155
- :wgroup_id => Number, :dgroup_id => Number, :custom_base => Boolean, :klass => String,
156
- :m_text => String, :m_title => String, :m_author => String,
157
- :id => {:class => Number, :method => 'zip'},
158
- :skin => 'Skin', :lang => String, :content_lang => {:class => String, :nil => true},
159
- :visitor => 'User',
160
- [:ancestor?, Node] => Boolean
161
- safe_method :defaults => {:nil => true},
162
- :score => Number, :comments_count => Number,
163
- :custom_a => Number, :custom_b => Number
164
-
165
- # same with parent_zip, section_zip, etc...
160
+ # Must come after Property
161
+ include Zena::Use::FieldIndex::ModelMethods
166
162
 
163
+ include RubyLess
167
164
 
168
165
  # This is used to load roles in an instance or on a class during compilation. Module
169
166
  # inclusion has to come *after* RubyLess because we overwrite safe_method_type.
@@ -173,12 +170,10 @@ class Node < ActiveRecord::Base
173
170
  has_many :discussions, :dependent => :destroy
174
171
  has_many :links
175
172
  has_and_belongs_to_many :cached_pages
176
- belongs_to :virtual_class, :foreign_key => 'vclass_id'
177
173
  belongs_to :site
178
174
  belongs_to :skin
179
175
  before_validation :set_defaults
180
176
  before_validation :node_before_validation
181
- validates_presence_of :node_name
182
177
  validate :validate_node
183
178
  before_create :node_before_create
184
179
  before_save :change_klass
@@ -191,25 +186,58 @@ class Node < ActiveRecord::Base
191
186
  # A possible solution could be to use the other syntax exclusively ('rel' => {'friend' => [4,5,6]})
192
187
  include Zena::Use::NestedAttributesAlias::ModelMethods
193
188
 
194
- #nested_attributes_alias %r{^v_(\w+)} => ['version']
195
- #nested_attributes_alias %r{^c_(\w+)} => ['version', 'content']
196
- #nested_attributes_alias %r{^d_(\w+)} => ['version', 'dyn']
189
+ # Dynamic resolution of the author class from the user prototype
190
+ def self.author_proc
191
+ Proc.new do |h, r, s|
192
+ res = {:method => 'author', :nil => true}
193
+ if prototype = visitor.prototype
194
+ res[:class] = prototype.vclass
195
+ else
196
+ res[:class] = VirtualClass['Node']
197
+ end
198
+ res
199
+ end
200
+ end
201
+
202
+ safe_property :title, :text, :summary
197
203
 
198
- safe_context :parent => 'Node', :project => 'Project', :section => 'Section',
199
- :real_project => 'Project', :real_section => 'Section',
204
+ safe_attribute :created_at, :updated_at, :event_at, :log_at, :publish_from, :basepath, :inherit, :position
205
+
206
+
207
+ # safe_node_context defined in Enrollable
208
+ safe_node_context :parent => 'Node', :project => 'Project', :section => 'Section',
209
+ :real_project => 'Project', :real_section => 'Section'
210
+
211
+ safe_context :custom_a => Number, :custom_b => Number, #, :score => Number
200
212
  :comments => ['Comment'],
213
+ # Code language for syntax highlighting
214
+ :content_lang => String,
201
215
  :data => {:class => ['DataEntry'], :zafu => {:data_root => 'node_a'}},
202
216
  :data_a => {:class => ['DataEntry'], :zafu => {:data_root => 'node_a'}},
203
217
  :data_b => {:class => ['DataEntry'], :zafu => {:data_root => 'node_b'}},
204
218
  :data_c => {:class => ['DataEntry'], :zafu => {:data_root => 'node_c'}},
205
219
  :data_d => {:class => ['DataEntry'], :zafu => {:data_root => 'node_d'}},
206
- :traductions => ['Version'],
207
- :discussion => 'Discussion'
208
- safe_method :v => {:class => 'Version', :method => 'version'},
220
+ :traductions => ['Version'], :discussion => 'Discussion'
221
+
222
+ # we use safe_method because the columns can be null, but the values are never null
223
+ safe_method :kpath => String, :user_zip => Number,
224
+ :parent_zip => Number, :project_zip => Number, :section_zip => Number,
225
+ :ref_lang => String,
226
+ :position => Number, :rgroup_id => Number,
227
+ :wgroup_id => Number, :dgroup_id => Number, :custom_base => Boolean,
228
+ :klass => String,
229
+ :m_text => String, :m_title => String, :m_author => String,
230
+ :id => {:class => Number, :method => 'zip'},
231
+ :skin => 'Skin', :ref_lang => String,
232
+ :visitor => 'User', [:ancestor?, Node] => Boolean,
233
+ :comments_count => Number,
234
+ :v => {:class => 'Version', :method => 'version'},
209
235
  :version => 'Version', :v_status => Number, :v_lang => String,
210
236
  :v_publish_from => Time, :v_backup => Boolean,
211
237
  :zip => Number, :parent_id => {:class => Number, :nil => true, :method => 'parent_zip'},
212
- :author => {:method => 'user', :class => 'User'}, :user => 'User'
238
+ :user => 'User',
239
+ :author => author_proc,
240
+ :vclass => {:class => 'VirtualClass', :method => 'virtual_class'}
213
241
 
214
242
  # This is needed so that we can use secure_scope and secure in search.
215
243
  extend Zena::Acts::Secure
@@ -221,13 +249,18 @@ class Node < ActiveRecord::Base
221
249
  has_multiple :versions, :inverse => 'node'
222
250
 
223
251
  include Zena::Use::Workflow
224
- include Zena::Use::NodeName # must be included after Workflow
225
- include Zena::Use::VersionHash
252
+ include Zena::Use::Ancestry::ModelMethods
226
253
 
227
254
  # to_xml
228
- include Zena::Acts::Serializable::NodeMethods
255
+ include Zena::Acts::Serializable::ModelMethods
256
+
257
+ # compute vhash (must come before Fulltext)
258
+ include Zena::Use::VersionHash::ModelMethods
259
+
260
+ # computed properties (vclass prop_eval)
261
+ include Zena::Use::PropEval::ModelMethods
229
262
 
230
- # fulltext indices
263
+ # fulltext indices (must come after PropEval)
231
264
  include Zena::Use::Fulltext::ModelMethods
232
265
 
233
266
  # List of version attributes that should be accessed as proxies 'v_lang', 'v_status', etc
@@ -262,16 +295,20 @@ class Node < ActiveRecord::Base
262
295
 
263
296
  include Zena::Use::Relations::ModelMethods
264
297
 
298
+ # model based indices (must come after Relations)
299
+ include Zena::Use::ScopeIndex::ModelMethods
300
+
265
301
  include Zena::Use::QueryNode::ModelMethods
266
302
 
267
303
  @@native_node_classes = {'N' => self}
304
+ @@native_node_classes_by_name = {'Node' => self}
268
305
  @@unhandled_children = []
269
- class << self
270
306
 
271
- def new(hash={})
307
+ class << self
308
+ def new(hash={}, vclass = nil)
272
309
  node = super()
273
- # set kpath before loading roles
274
- node.kpath = hash[:kpath] || self.kpath
310
+ # set virtual_class (acts as schema) before setting attributes
311
+ node.virtual_class = vclass || VirtualClass[self.name]
275
312
  node.attributes = hash
276
313
  node
277
314
  end
@@ -290,17 +327,66 @@ class Node < ActiveRecord::Base
290
327
  end
291
328
  end
292
329
 
330
+ def find_by_parent_title_and_kpath(parent_id, title, kpath = nil, opts = {})
331
+ if cond = opts[:conditions]
332
+ cond[0] = Array(cond[0])
333
+ else
334
+ cond = opts[:conditions] = [[]]
335
+ end
336
+
337
+ if kpath
338
+ cond[0] << "kpath like ?"
339
+ cond << "#{kpath}%"
340
+ end
341
+ cond[0] << "site_id = ? AND parent_id = ?"
342
+ cond << current_site.id << parent_id
343
+
344
+ find_by_title(title, opts)
345
+ end
346
+
347
+ # Find node by the indexed title.
348
+ def find_by_title(title, opts = {})
349
+ if cond = opts[:conditions]
350
+ cond[0] = Array(cond[0])
351
+ else
352
+ cond = opts[:conditions] = [[]]
353
+ end
354
+
355
+ if opts.delete(:like)
356
+ cond[0] << "id1.value LIKE ?"
357
+ else
358
+ cond[0] << "id1.value = ?"
359
+ end
360
+ cond << title
361
+
362
+ cond[0] = cond[0].join(' AND ')
363
+
364
+ opts[:joins] = Node.title_join
365
+ opts[:select] = 'nodes.*'
366
+
367
+ Node.find(:first, opts)
368
+ end
369
+
293
370
  # Return the list of (kpath,subclasses) for the current class.
294
371
  def native_classes
372
+ load_unhandled_children
373
+ @@native_node_classes
374
+ end
375
+
376
+ # Return the list of (name,class) for the current class.
377
+ def native_classes_by_name
378
+ load_unhandled_children
379
+ @@native_node_classes_by_name
380
+ end
381
+
382
+ def load_unhandled_children
295
383
  # this is to make sure subclasses are loaded before the first call
296
384
  # TODO: find a better way to make sure they are all loaded
297
- [Note,Page,Project,Section,Reference,BaseContact,Document,Image,TextDocument,Skin,Template]
298
-
385
+ [Note,Page,Project,Section,Document,Image,TextDocument,Skin,Template]
299
386
  while child = @@unhandled_children.pop
300
387
  @@native_node_classes[child.kpath] = child
388
+ @@native_node_classes_by_name[child.name] = child
301
389
  end
302
-
303
- @@native_node_classes.reject {|kpath,klass| !(kpath =~ /^#{self.kpath}/) }
304
390
  end
305
391
 
306
392
  # check inheritance chain through kpath
@@ -310,7 +396,7 @@ class Node < ActiveRecord::Base
310
396
 
311
397
  # Class list to which this class can change to
312
398
  def change_to_classes_for_form
313
- classes_for_form(:class => 'Node', :without => 'Document, BaseContact')
399
+ classes_for_form(:class => 'Node', :without => 'Document')
314
400
  end
315
401
 
316
402
  # List of classes that a node can change to.
@@ -318,50 +404,33 @@ class Node < ActiveRecord::Base
318
404
  change_to_classes_for_form.map {|k,v| v}
319
405
  end
320
406
 
321
- # FIXME: how to make sure all sub-classes of Node are loaded before this is called ?
407
+ # TODO: remove and use VirtualClass[...].classes_for_form directly
322
408
  def classes_for_form(opts={})
323
- if klass = opts.delete(:class)
324
- if klass = get_class(klass)
325
- klass.classes_for_form(opts)
326
- else
327
- return ['', ''] # bad class
328
- end
329
- else
330
- 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 ' ')
331
- end
409
+ VirtualClass[self.name].classes_for_form(opts)
332
410
  end
333
411
 
334
412
  # FIXME: how to make sure all sub-classes of Node are loaded before this is called ?
413
+ # TODO: move into helper
335
414
  def kpaths_for_form(opts={})
336
- all_classes(opts).map{|a,b| [a[1..-1].gsub(/./,'  ') + b.to_s, a.to_s] } # white spaces are insecable spaces (not ' ')
337
- end
338
-
339
- def all_classes(opts={})
340
- virtual_classes = VirtualClass.find(:all, :conditions => ["site_id = ? AND create_group_id IN (?) AND kpath LIKE '#{self.kpath}%'", current_site[:id], visitor.group_ids])
341
- classes = (virtual_classes.map{|r| [r.kpath, r.name]} + native_classes.to_a).sort{|a,b| a[0] <=> b[0]}
342
- if opts[:without]
343
- reject_kpath = opts[:without].split(',').map(&:strip).map {|name| Node.get_class(name) }.compact.map { |kla| kla.kpath }.join('|')
344
- classes.reject! {|k,c| k =~ /^#{reject_kpath}/ }
415
+ VirtualClass.all_classes(opts).map do |vclass|
416
+ # white spaces are insecable spaces (not ' ')
417
+ a, b = vclass.kpath, vclass.name
418
+ [a[1..-1].gsub(/./,'  ') + b, a]
345
419
  end
346
- classes
347
420
  end
348
421
 
349
422
  # Return class or virtual class from name.
423
+ # FIXME: remove once everything can use VirtualClass[name]
350
424
  def get_class(rel, opts={})
351
425
  # mushroom_types ==> MushroomType
352
426
  class_name = rel =~ /\A[a-z]/ ? rel.singularize.camelize : rel
353
- begin
354
- klass = Module.const_get(class_name)
355
- raise NameError unless klass.ancestors.include?(Node)
356
- rescue NameError
357
- # find the virtual class
358
- if opts[:create]
359
- klass = VirtualClass.find(:first, :conditions=>["site_id = ? AND create_group_id IN (?) AND name = ?",current_site[:id], visitor.group_ids, class_name])
360
- else
361
- klass = VirtualClass.find(:first, :conditions=>["site_id = ? AND name = ?",current_site[:id], class_name])
362
- end
427
+ vclass = VirtualClass.find_by_name(class_name)
428
+ if opts[:create] && vclass.id
429
+ # TODO: how do we deal with real class ? (Currently = pass).
430
+ visitor.group_ids.include?(vclass.create_group_id) ? vclass : nil
431
+ else
432
+ vclass
363
433
  end
364
- klass
365
434
  end
366
435
 
367
436
  # Find a role by name.
@@ -371,19 +440,6 @@ class Node < ActiveRecord::Base
371
440
  Role.first(:conditions => ['name = ? AND site_id = ?', role_name, current_site.id])
372
441
  end
373
442
 
374
- # Return a new object of the class name or nil if the class name does not exist.
375
- def new_from_class(rel)
376
- if k = get_class(rel, :create => true)
377
- k.new_instance
378
- else
379
- nil
380
- end
381
- end
382
-
383
- def get_class_from_kpath(kp)
384
- native_classes[kp] || VirtualClass.find(:first, :conditions=>["site_id = ? AND kpath = ?",current_site[:id], kp])
385
- end
386
-
387
443
  # Find a node's attribute based on a pseudo (id or path). Used by zazen to create a link for ""::art or "":(people/ant) for example.
388
444
  def translate_pseudo_id(id, sym = :id, base_node = nil)
389
445
  if id.to_s =~ /\A(-?)(\d+)\Z/
@@ -408,11 +464,32 @@ class Node < ActiveRecord::Base
408
464
  elsif str =~ /\A:?([0-9a-zA-Z ]+)(\+*)\Z/
409
465
  offset = $2.to_s.size
410
466
  Node.search_records($1.gsub('-',' '), :offset => offset, :limit => 1).first
411
- elsif path = str[/\A\(([^\)]*)\)\Z/,1]
467
+ elsif path = str[/\A\(([^\)]+)\)\Z/,1]
412
468
  if path[0..0] == '/'
413
- find_by_path(path[1..-1])
469
+ path = path[1..-1].split('/').map {|p| String.from_filename(p) }
470
+ find_by_path(path)
414
471
  elsif base_node
415
- find_by_path(path.abs_path(base_node.fullpath))
472
+ # transform ../../foo and 45/32/61/72 ==> 'foo' and 45/32
473
+ path = path.split('/')
474
+ root = base_node.fullpath.split('/')
475
+ while path[0] == '..'
476
+ root.pop
477
+ path.shift
478
+ end
479
+
480
+ path = path.map {|p| String.from_filename(p) }
481
+
482
+ if base_node.zip == root.last.to_i
483
+ find_by_path(path, base_node.id)
484
+ elsif root.last
485
+ if base = find_by_zip(root.last)
486
+ find_by_path(path, base.id)
487
+ else
488
+ nil
489
+ end
490
+ else
491
+ find_by_path(path)
492
+ end
416
493
  else
417
494
  # do not use (path) pseudo when there is no base_node (during create_or_update_node for example).
418
495
  # FIXME: path pseudo is needed for links... and it should be done here (egg and hen problem)
@@ -430,23 +507,32 @@ class Node < ActiveRecord::Base
430
507
 
431
508
  def create_or_update_node(new_attributes)
432
509
  attributes = transform_attributes(new_attributes)
433
- unless attributes['node_name'] && attributes['parent_id']
434
- node = Node.new
435
- node.errors.add('node_name', "can't be blank") unless attributes['node_name']
436
- node.errors.add('parent_id', "can't be blank") unless attributes['parent_id']
437
- return node
510
+
511
+ v_lang = attributes['v_lang']
512
+ if !current_site.lang_list.include?(v_lang)
513
+ attributes['v_lang'] = current_site.lang_list.first
438
514
  end
439
515
 
440
- begin
441
- klass = Node.get_class(attributes['klass'] || 'Node')
442
- klass = klass.real_class if klass.kind_of?(VirtualClass)
443
- rescue NameError
444
- klass = Node
516
+ if zip = attributes.delete('parent_zip')
517
+ if id = secure(Node) { Node.translate_pseudo_id(zip, :id, self) }
518
+ attributes['parent_id'] = id
519
+ else
520
+ node = Node.new
521
+ node.errors.add('parent_id', 'could not be found')
522
+ return node
523
+ end
524
+ end
525
+
526
+ unless attributes['title'] && attributes['parent_id']
527
+ node = Node.new
528
+ node.errors.add('title', "can't be blank") if attributes['title'].blank?
529
+ node.errors.add('parent_id', "can't be blank") if attributes['parent_id'].blank?
530
+ return node
445
531
  end
446
532
 
447
533
  # FIXME: remove 'with_exclusive_scope' once scopes are clarified and removed from 'secure'
448
- node = klass.send(:with_exclusive_scope) do
449
- Zena::Db.insensitive_find(klass, :first, :site_id => current_site[:id], :parent_id => attributes['parent_id'], :node_name => attributes['node_name'].url_name)
534
+ node = Node.send(:with_exclusive_scope) do
535
+ find_by_parent_title_and_kpath(attributes['parent_id'], attributes['title'], nil)
450
536
  end
451
537
 
452
538
  if node
@@ -463,7 +549,7 @@ class Node < ActiveRecord::Base
463
549
  node[:create_or_update] = 'same'
464
550
  end
465
551
  else
466
- node = create_node(new_attributes)
552
+ node = create_node(attributes, false)
467
553
  node[:create_or_update] = 'new'
468
554
  end
469
555
 
@@ -471,31 +557,38 @@ class Node < ActiveRecord::Base
471
557
  end
472
558
 
473
559
  # TODO: cleanup and rename with something indicating the attrs cleanup that this method does.
474
- def create_node(new_attributes)
475
- attributes = transform_attributes(new_attributes)
476
-
477
- # TODO: replace this hack with a proper class method 'secure' behaving like the
478
- # instance method. It would get the visitor and scope from the same hack below.
479
- scope = self.scoped_methods[0] || {}
560
+ def new_node(new_attributes, transform = true)
561
+ attributes = transform ? transform_attributes(new_attributes) : new_attributes
480
562
 
481
- klass_name = attributes.delete('class') || attributes.delete('klass') || 'Page'
482
- unless klass = get_class(klass_name, :create => true)
483
- node = Node.new
484
- node.instance_eval { @attributes = attributes }
485
- node.errors.add('klass', 'invalid')
486
- # This is to show the klass in the form seizure
487
- node.instance_variable_set(:@klass, klass_name.to_s)
488
- def node.klass; @klass; end
489
- return node
563
+ klass_name = attributes.delete('class') || attributes.delete('klass') || 'Page'
564
+ if klass_name.kind_of?(VirtualClass) || klass_name.kind_of?(Class)
565
+ klass = klass_name
566
+ else
567
+ unless klass = get_class(klass_name, :create => true)
568
+ node = Node.new
569
+ node.instance_eval { @attributes.merge!(attributes) }
570
+ node.errors.add('klass', 'invalid')
571
+ # This is to show the klass in the form seizure
572
+ node.instance_variable_set(:@klass, klass_name.to_s)
573
+ def node.klass; @klass; end
574
+ return node
575
+ end
490
576
  end
491
577
 
492
- if klass != self
493
- # FIXME: remove 'with_exclusive_scope' once scopes are clarified and removed from 'secure'
494
- node = klass.send(:with_exclusive_scope, scope) { klass.create_instance(attributes) }
578
+ if klass.kind_of?(VirtualClass)
579
+ node = secure(klass.real_class) { klass.new_instance(attributes) }
495
580
  else
496
- node = self.create_instance(attributes)
581
+ node = secure(klass) { klass.new_instance(attributes) }
497
582
  end
583
+ node
584
+ end
498
585
 
586
+ # TODO: cleanup and rename with something indicating the attrs cleanup that this method does.
587
+ def create_node(new_attributes, transform = true)
588
+ node = new_node(new_attributes, transform)
589
+ if node.errors.empty?
590
+ node.save
591
+ end
499
592
  node
500
593
  end
501
594
 
@@ -508,7 +601,7 @@ class Node < ActiveRecord::Base
508
601
  parent_id = opts[:parent_id] || opts[:parent][:id]
509
602
  folder = opts[:folder]
510
603
  defaults = (opts[:defaults] || {}).stringify_keys
511
- klass = opts[:klass] || "Page"
604
+ klass = opts[:class] || opts[:klass] || "Page"
512
605
  res = {}
513
606
 
514
607
  unless folder
@@ -521,7 +614,9 @@ class Node < ActiveRecord::Base
521
614
  return res
522
615
  end
523
616
 
524
- entries = Dir.entries(folder).reject { |f| f =~ /^([\._~]|[^\w])/ }.sort
617
+ entries = Dir.entries(folder).reject { |f| f =~ /^([\._~])/ }.map do |filename|
618
+ String.from_filename(filename)
619
+ end.sort
525
620
 
526
621
  index = 0
527
622
 
@@ -534,24 +629,24 @@ class Node < ActiveRecord::Base
534
629
 
535
630
  if File.stat(path).directory?
536
631
  type = :folder
537
- node_name = filename
632
+ title = filename
538
633
  sub_folder = path
539
634
  attrs = defaults.dup
540
635
  attrs['v_lang'] ||= visitor.lang
541
636
  elsif filename =~ /^(.+?)(\.\w\w|)(\.\d+|)\.zml$/ # bird.jpg.en.zml
542
637
  # node content in yaml
543
638
  type = :node
544
- node_name = "#{$1}#{$4}"
639
+ title = "#{$1}#{$4}"
545
640
  lang = $2.blank? ? nil : $2[1..-1]
546
641
 
547
642
  # no need for base_node (this is done after all with parse_assets in the controller)
548
643
  attrs = defaults.merge(get_attributes_from_yaml(path))
549
- attrs['node_name'] = node_name
644
+ attrs['title'] = title
550
645
  attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
551
646
  versions << attrs
552
647
  elsif filename =~ /^((.+?)\.(.+?))(\.\w\w|)(\.\d+|)$/ # bird.jpg.en
553
648
  type = :document
554
- node_name = $1
649
+ title = $1
555
650
  attrs = defaults.dup
556
651
  lang = $4.blank? ? nil : $4[1..-1]
557
652
  attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
@@ -560,15 +655,15 @@ class Node < ActiveRecord::Base
560
655
  end
561
656
 
562
657
  index += 1
563
- while entries[index] =~ /^#{node_name}(\.\w\w|)(\.\d+|)\.zml$/ # bird.jpg.en.zml
658
+ while entries[index] =~ /^#{title}(\.\w\w|)(\.\d+|)\.zml$/ # bird.jpg.en.zml
564
659
  lang = $1.blank? ? visitor.lang : $1[1..-1]
565
660
  path = File.join(folder,entries[index])
566
661
 
567
662
  # we have a zml file. Create a version with this file
568
663
  # no need for base_node (this is done after all with parse_assets in the controller)
569
664
  attrs = defaults.merge(get_attributes_from_yaml(path))
570
- attrs['node_name'] = node_name
571
- attrs['v_lang'] ||= lang
665
+ attrs['title'] ||= title
666
+ attrs['v_lang'] ||= lang
572
667
  versions << attrs
573
668
 
574
669
  index += 1
@@ -577,14 +672,14 @@ class Node < ActiveRecord::Base
577
672
  if versions.empty?
578
673
  if type == :folder
579
674
  # minimal node for a folder
580
- attrs['node_name'] = node_name
581
- attrs['v_lang'] ||= lang
582
- attrs['class'] = klass
675
+ attrs['title'] = title
676
+ attrs['v_lang'] ||= lang
677
+ attrs['class'] = klass
583
678
  versions << attrs
584
679
  elsif type == :document
585
680
  # minimal node for a document
586
- attrs['node_name'] = node_name
587
- attrs['v_lang'] ||= lang
681
+ attrs['title'] = title
682
+ attrs['v_lang'] ||= lang
588
683
  versions << attrs
589
684
  end
590
685
  end
@@ -594,14 +689,14 @@ class Node < ActiveRecord::Base
594
689
  # FIXME: same lang: remove before update current_obj.remove if current_obj.v_lang == attrs['v_lang'] && current_obj.v_status != Zena::Status[:red]
595
690
  # FIXME: current_obj.publish if attrs['v_status'].to_i == Zena::Status[:pub]
596
691
  if type == :document
597
- attrs['node_name' ] = attrs['node_name'].split('.')[0..-2].join('.')
692
+ attrs['title' ] = attrs['title'].split('.')[0..-2].join('.')
598
693
  if document_path
599
694
  attrs['ext'] ||= document_path.split('.').last
600
695
  # file
601
696
  insert_zafu_headings = false
602
- if opts[:parent_class] == 'Skin' && ['html','xhtml'].include?(attrs['ext']) && attrs['node_name'] == 'index'
603
- attrs['ext'] = 'zafu'
604
- attrs['node_name'] = 'Node'
697
+ if opts[:parent_class] == 'Skin' && ['html','xhtml'].include?(attrs['ext']) && attrs['title'] == 'index'
698
+ attrs['ext'] = 'zafu'
699
+ attrs['title'] = 'Node'
605
700
  insert_zafu_headings = true
606
701
  end
607
702
 
@@ -690,14 +785,6 @@ class Node < ActiveRecord::Base
690
785
  node
691
786
  end
692
787
 
693
- # Find a node by it's full path. Cache 'fullpath' if found. This is useless now
694
- # that fullpath is properly cached. REMOVE !
695
- def find_by_path(path)
696
- return nil unless scope = scoped_methods[0]
697
- return nil unless scope[:find] # not secured find. refuse.
698
- Zena::Db.insensitive_find(Node, :first, :fullpath => path)
699
- end
700
-
701
788
  # FIXME: Where is this used ?
702
789
  def class_for_relation(rel)
703
790
  case rel
@@ -727,7 +814,7 @@ class Node < ActiveRecord::Base
727
814
 
728
815
  # Translate attributes from the visitor's reference to the application.
729
816
  # This method translates dates, zazen shortcuts and zips and returns a stringified hash.
730
- def transform_attributes(new_attributes, base_node = nil, change_timezone = true)
817
+ def transform_attributes(new_attributes, base_node = nil, change_timezone = true, is_link = false)
731
818
  res = {}
732
819
  res['parent_id'] = new_attributes[:_parent_id] if new_attributes[:_parent_id] # real id set inside zena.
733
820
 
@@ -740,11 +827,11 @@ class Node < ActiveRecord::Base
740
827
  end
741
828
 
742
829
  if !res['parent_id'] && p = attributes['parent_id']
743
- res['parent_id'] = Node.translate_pseudo_id(p, :id, base_node) || p
830
+ res['parent_zip'] = p
744
831
  end
745
832
 
746
833
  attributes.each do |key, value|
747
- next if ['_parent_id', 'parent_id'].include?(key)
834
+ next if ['parent_id', 'parent_zip', '_parent_id'].include?(key)
748
835
 
749
836
  if %w{rgroup_id wgroup_id dgroup_id}.include?(key)
750
837
  res[key] = Group.translate_pseudo_id(value, :id) || value
@@ -752,9 +839,11 @@ class Node < ActiveRecord::Base
752
839
  res["#{key}_id"] = Group.translate_pseudo_id(value, :id) || value
753
840
  elsif %w{user_id}.include?(key)
754
841
  res[key] = User.translate_pseudo_id(value, :id) || value
755
- elsif %w{create_at updated_at}.include?(key)
842
+ elsif %w{id create_at updated_at}.include?(key)
756
843
  # ignore (can be present in xml)
757
- elsif %w{log_at event_at v_publish_from date}.include?(key)
844
+ elsif %w{log_at event_at v_publish_from}.include?(key) || (is_link && %w{date}.include?(key))
845
+ # FIXME: !!! We need to fix timezone parsing in dates depending on the Schema used. This means
846
+ # that we probably need to do this at the property level (during write).
758
847
  if value.kind_of?(Time)
759
848
  res[key] = value
760
849
  elsif value
@@ -767,26 +856,15 @@ class Node < ActiveRecord::Base
767
856
  end
768
857
  end
769
858
  elsif key =~ /^(\w+)_id$/
770
- if key[0..1] == 'd_'
771
- res[key] = Node.translate_pseudo_id(value, :zip, base_node) || value
772
- else
773
- res[key] = Node.translate_pseudo_id(value, :id, base_node) || value
774
- end
859
+ res["#{$1}_zip"] = value
775
860
  elsif key =~ /^(\w+)_ids$/
776
- # Id list. Bad ids are removed.
777
- values = value.kind_of?(Array) ? value : value.split(',')
778
- if key[0..1] == 'd_'
779
- values.map! {|v| Node.translate_pseudo_id(v, :zip, base_node) }
780
- else
781
- values.map! {|v| Node.translate_pseudo_id(v, :id, base_node) }
782
- end
783
- res[key] = values.compact
861
+ res["#{$1}_zips"] = value.kind_of?(Array) ? value : value.split(',')
784
862
  elsif key == 'file'
785
863
  unless value.blank?
786
864
  res[key] = value
787
865
  end
788
866
  elsif value.kind_of?(Hash)
789
- res[key] = transform_attributes(value, base_node, change_timezone)
867
+ res[key] = transform_attributes(value, base_node, change_timezone, %w{link rel rel_attributes}.include?(key) || is_link)
790
868
  else
791
869
  # translate zazen
792
870
  if value.kind_of?(String)
@@ -814,6 +892,9 @@ class Node < ActiveRecord::Base
814
892
  method = signature.first
815
893
  if type = super
816
894
  type
895
+ elsif method == 'cached_role_ids'
896
+ # TODO: how to avoid everything ending in '_id' being caught as relations ?
897
+ nil
817
898
  elsif method =~ /^(.+)_((id|zip|status|comment)(s?))\Z/ && !instance_methods.include?(method)
818
899
  key = $3 == 'id' ? "zip#{$4}" : $2
819
900
  {:method => "rel[#{$1.inspect}].try(:other_#{key})", :nil => true, :class => ($4.blank? ? Number : [Number])}
@@ -849,6 +930,13 @@ class Node < ActiveRecord::Base
849
930
  end
850
931
  end
851
932
 
933
+ # Remove loaded version and properties on reload.
934
+ def reload
935
+ @version = nil
936
+ @properties = nil
937
+ super
938
+ end
939
+
852
940
  # TODO: remove when :inverse_of works.
853
941
  def versions_with_secure(*args)
854
942
  proxy = versions_without_secure(*args)
@@ -869,6 +957,8 @@ class Node < ActiveRecord::Base
869
957
  end
870
958
 
871
959
  # virtual class
960
+ # FIXME: alias vclass to virtual_class
961
+ # alias vclass virtual_class
872
962
  def vclass
873
963
  virtual_class || self.class
874
964
  end
@@ -902,7 +992,6 @@ class Node < ActiveRecord::Base
902
992
 
903
993
  # Replace [id], [title], etc in attributes values
904
994
  def replace_attributes_in_values(hash)
905
- load_roles!
906
995
  hash.each do |k,v|
907
996
  hash[k] = safe_eval_string(v)
908
997
  end
@@ -920,66 +1009,6 @@ class Node < ActiveRecord::Base
920
1009
  ZazenParser.new(text,:helper=>helper).render(:translate_ids => :relative_path, :node=>self)
921
1010
  end
922
1011
 
923
- # Return the list of ancestors (without self): [root, obj, obj]
924
- # ancestors to which the visitor has no access are removed from the list
925
- def ancestors(start=[])
926
- raise Zena::InvalidRecord, "Infinit loop in 'ancestors' (#{start.inspect} --> #{self[:id]})" if start.include?(self[:id])
927
- start += [self[:id]]
928
- if self[:id] == current_site[:root_id]
929
- []
930
- elsif self[:parent_id].nil?
931
- []
932
- else
933
- parent = @parent || Node.find(self[:parent_id])
934
- parent.visitor = visitor
935
- if parent.can_read?
936
- parent.ancestors(start) + [parent]
937
- else
938
- parent.ancestors(start)
939
- end
940
- end
941
- end
942
-
943
- # Return true if the current node is an ancestor for the given child
944
- def ancestor?(child)
945
- child.fullpath =~ %r{\A#{fullpath}}
946
- end
947
-
948
- # url base path. If a node is in 'projects' and projects has custom_base set, the
949
- # node's basepath becomes 'projects', so the url will be 'projects/node34.html'.
950
- # The basepath is cached. If rebuild is set to true, the cache is updated.
951
- def basepath
952
- self[:basepath]
953
- end
954
-
955
- # Same as fullpath, but the path includes the root node.
956
- def rootpath
957
- current_site.name + (fullpath != "" ? "/#{fullpath}" : "")
958
- end
959
-
960
- alias path rootpath
961
-
962
- # Return an array with the node name and the last two parents' names.
963
- def short_path
964
- path = self.rootpath.split('/')
965
- if path.size > 2
966
- ['..'] + path[-2..-1]
967
- else
968
- path
969
- end
970
- end
971
-
972
- def pseudo_id(root_node, sym)
973
- case sym
974
- when :zip
975
- self.zip
976
- when :relative_path
977
- full = self.fullpath
978
- root = root_node ? root_node.fullpath : ''
979
- "(#{full.rel_path(root)})"
980
- end
981
- end
982
-
983
1012
  # Return save path for an asset (element produced by text like a png file from LateX)
984
1013
  def asset_path(asset_filename)
985
1014
  # It would be nice to move this outside 'self[:id]' so that the same asset can
@@ -1067,8 +1096,7 @@ class Node < ActiveRecord::Base
1067
1096
 
1068
1097
  # Create a child and let him inherit from rwp groups and section_id
1069
1098
  def new_child(opts={})
1070
- klass = opts.delete(:class) || Page
1071
- c = klass.new(opts)
1099
+ c = Node.new_node(opts)
1072
1100
  c.parent_id = self[:id]
1073
1101
  c.instance_variable_set(:@parent, self)
1074
1102
 
@@ -1085,9 +1113,8 @@ class Node < ActiveRecord::Base
1085
1113
  end
1086
1114
 
1087
1115
  # ACCESSORS
1088
- # FIXME: remove
1089
1116
  def author
1090
- user.contact
1117
+ user.node
1091
1118
  end
1092
1119
 
1093
1120
  alias o_user user
@@ -1117,12 +1144,6 @@ class Node < ActiveRecord::Base
1117
1144
  end
1118
1145
  end
1119
1146
 
1120
- # set node_name: remove all accents and camelize
1121
- def node_name=(str)
1122
- return unless str && str != ""
1123
- self[:node_name] = str.url_name
1124
- end
1125
-
1126
1147
  # Return current discussion id (used by query_builder)
1127
1148
  def get_discussion_id
1128
1149
  (discussion && !discussion.new_record?) ? discussion[:id] : '0'
@@ -1142,7 +1163,21 @@ class Node < ActiveRecord::Base
1142
1163
 
1143
1164
  # Id to zip mapping for parent_id. Used by zafu and forms.
1144
1165
  def parent_zip
1145
- parent ? parent[:zip] : nil
1166
+ @parent_zip || parent.try(:zip)
1167
+ end
1168
+
1169
+ # When setting parent trough controllers, we receive parent_zip=.
1170
+ def parent_zip=(zip)
1171
+ @parent_zip = zip
1172
+ end
1173
+
1174
+ # When setting skin trough controllers, we receive skin_zip=.
1175
+ def skin_zip=(zip)
1176
+ @skin_zip = zip.to_i
1177
+ end
1178
+
1179
+ def skin_zip
1180
+ @skin_zip || skin.try(:zip)
1146
1181
  end
1147
1182
 
1148
1183
  # Id to zip mapping for section_id. Used by zafu and forms.
@@ -1323,14 +1358,15 @@ class Node < ActiveRecord::Base
1323
1358
  n = 0
1324
1359
  while true
1325
1360
  folder_path = File.join(RAILS_ROOT, 'tmp', sprintf('%s.%d.%d', 'archive', $$, n))
1361
+ n += 1
1326
1362
  break unless File.exists?(folder_path)
1327
1363
  end
1328
1364
 
1329
1365
  begin
1330
1366
  FileUtils::mkpath(folder_path)
1331
1367
  export_to_folder(folder_path)
1332
- tempf = Tempfile.new(node_name)
1333
- `cd #{folder_path}; tar czf #{tempf.path} *`
1368
+ tempf = Tempfile.new(title.to_filename)
1369
+ `cd #{folder_path.inspect}; tar czf #{tempf.path.inspect} *`
1334
1370
  ensure
1335
1371
  FileUtils::rmtree(folder_path)
1336
1372
  end
@@ -1341,13 +1377,13 @@ class Node < ActiveRecord::Base
1341
1377
  def export_to_folder(path)
1342
1378
  children = secure(Node) { Node.find(:all, :conditions=>['parent_id = ?', self[:id] ]) }
1343
1379
 
1344
- if kind_of?(Document) && title == node_name && (kind_of?(TextDocument) || text.blank? || text == "!#{zip}!")
1380
+ if kind_of?(Document) && (kind_of?(TextDocument) || text.blank? || text == "!#{zip}!")
1345
1381
  # skip zml
1346
1382
  # TODO: this should better check that version content is really useless
1347
- elsif title == node_name && text.blank? && klass == 'Page' && children
1383
+ elsif text.blank? && klass == 'Page' && children
1348
1384
  # skip zml
1349
1385
  else
1350
- File.open(File.join(path, node_name + '.zml'), 'wb') do |f|
1386
+ File.open(File.join(path, title.to_filename + '.zml'), 'wb') do |f|
1351
1387
  f.puts self.to_yaml
1352
1388
  end
1353
1389
  end
@@ -1358,8 +1394,10 @@ class Node < ActiveRecord::Base
1358
1394
  end
1359
1395
 
1360
1396
  if children
1361
- content_folder = File.join(path, node_name)
1362
- FileUtils::mkpath(content_folder)
1397
+ content_folder = File.join(path, title.to_filename)
1398
+ if !FileUtils::mkpath(content_folder)
1399
+ puts "Problem..."
1400
+ end
1363
1401
  children.each do |child|
1364
1402
  child.export_to_folder(content_folder)
1365
1403
  end
@@ -1406,10 +1444,7 @@ class Node < ActiveRecord::Base
1406
1444
  # Safe dynamic method dispatching when the method is not known during compile time. Currently this
1407
1445
  # only works for methods without arguments.
1408
1446
  def safe_send(method)
1409
- # We need to load roles or all properties will be ignored
1410
- load_roles!
1411
-
1412
- return nil unless type = safe_method_type([method])
1447
+ return nil unless type = virtual_class.safe_method_type([method])
1413
1448
  res = eval(type[:method])
1414
1449
  res ? res.to_s : nil
1415
1450
  end
@@ -1417,7 +1452,11 @@ class Node < ActiveRecord::Base
1417
1452
  protected
1418
1453
 
1419
1454
  # after node is saved, make sure it's children have the correct section set
1455
+ # FIXME: move this into Ancestry
1420
1456
  def spread_project_and_section
1457
+ # clear parent_zip
1458
+ @parent_zip = nil
1459
+
1421
1460
  if @spread_section_id || @spread_project_id
1422
1461
  # update children
1423
1462
  sync_section_and_project(@spread_section_id, @spread_project_id)
@@ -1478,14 +1517,32 @@ class Node < ActiveRecord::Base
1478
1517
 
1479
1518
  private
1480
1519
  def set_defaults
1481
- self.title = self.node_name if title.blank?
1482
- self.node_name = self.title if node_name.blank?
1483
-
1484
1520
  self[:custom_base] = false unless kind_of?(Page)
1485
1521
  true
1486
1522
  end
1487
1523
 
1488
1524
  def node_before_validation
1525
+ if @parent_zip
1526
+ if id = secure(Node) { Node.translate_pseudo_id(@parent_zip, :id, new_record? ? nil : self) }
1527
+ self.parent_id = id
1528
+ else
1529
+ @parent_zip_error = _('could not be found')
1530
+ end
1531
+ end
1532
+
1533
+ if @skin_zip
1534
+ if node = secure(Node) { Node.find_by_zip(@skin_zip) }
1535
+ if !node.kind_of?(Skin)
1536
+ @skin_zip_error = _('type mismatch (%{type} is not a Skin)') % {:type => node.klass}
1537
+ else
1538
+ self.skin_id = node.id
1539
+ end
1540
+ else
1541
+ @skin_zip_error = _('could not be found')
1542
+ end
1543
+ end
1544
+
1545
+
1489
1546
  self[:kpath] = self.vclass.kpath
1490
1547
 
1491
1548
  # make sure section is the same as the parent
@@ -1529,6 +1586,18 @@ class Node < ActiveRecord::Base
1529
1586
 
1530
1587
  # Make sure the node is complete before creating it (check parent and project references)
1531
1588
  def validate_node
1589
+ errors.add(:title, "can't be blank") if title.blank?
1590
+
1591
+ if @parent_zip_error
1592
+ errors.add('parent_id', @parent_zip_error)
1593
+ @parent_zip_error = nil
1594
+ end
1595
+
1596
+ if @skin_zip_error
1597
+ errors.add('skin_id', @skin_zip_error)
1598
+ @skin_zip_error = nil
1599
+ end
1600
+
1532
1601
  # when creating root node, self[:id] and :root_id are both nil, so it works.
1533
1602
  if parent_id_changed? && self[:id] == current_site[:root_id]
1534
1603
  errors.add("parent_id", "root should not have a parent") unless self[:parent_id].blank?
@@ -1659,7 +1728,7 @@ class Node < ActiveRecord::Base
1659
1728
  def change_klass
1660
1729
  if @new_klass && !new_record?
1661
1730
  old_kpath = self.kpath
1662
-
1731
+ # FIXME ! (new virtual_class as schema...)
1663
1732
  klass = Node.get_class(@new_klass)
1664
1733
  if klass.kind_of?(VirtualClass)
1665
1734
  self[:vclass_id] = klass.kind_of?(VirtualClass) ? klass[:id] : nil
@@ -1716,26 +1785,21 @@ class Node < ActiveRecord::Base
1716
1785
  end
1717
1786
  end
1718
1787
 
1719
- def get_unique_node_name_in_scope(kpath)
1720
-
1721
- if node_name_changed? || parent_id_changed? || kpath_changed?
1788
+ def get_unique_title_in_scope(kpath)
1789
+ if prop.title_changed? || parent_id_changed? || kpath_changed?
1722
1790
  Node.send(:with_exclusive_scope) do
1723
- if new_record?
1724
- cond = ["node_name = ? AND parent_id = ? AND kpath LIKE ?", self[:node_name], self[:parent_id], kpath]
1791
+ if !new_record?
1792
+ cond = ['nodes.id != ?', id]
1725
1793
  else
1726
- cond = ["node_name = ? AND parent_id = ? AND kpath LIKE ? AND id != ? ", self[:node_name], self[:parent_id], kpath, self[:id]]
1794
+ cond = nil
1727
1795
  end
1728
1796
 
1729
- if taken_name = Node.find(:first, :select => 'node_name', :conditions => cond, :order => "LENGTH(node_name) DESC")
1730
- if taken_name =~ /^#{self[:node_name]}-(\d)/
1731
- self[:node_name] = "#{self[:node_name]}-#{$1.to_i + 1}"
1732
- i = $1.to_i + 1
1797
+ if taken_name = Node.find_by_parent_title_and_kpath(parent_id, title, kpath, :order => "LENGTH(id1.value) DESC", :select => 'id1.value', :conditions => cond)
1798
+ if taken_name =~ /^#{title}-(\d)/
1799
+ self.title = "#{title}-#{$1.to_i + 1}"
1733
1800
  else
1734
- self[:node_name] = "#{self[:node_name]}-1"
1735
- i = 1
1801
+ self.title = "#{title}-1"
1736
1802
  end
1737
-
1738
- self.title = "#{title}-#{i}" unless title.blank?
1739
1803
  end
1740
1804
  end
1741
1805
  end