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.
- data/History.txt +29 -0
- data/Rakefile +2 -0
- data/TODO_ZENA_1_0 +13 -23
- data/app/controllers/columns_controller.rb +1 -1
- data/app/controllers/comments_controller.rb +4 -3
- data/app/controllers/documents_controller.rb +8 -11
- data/app/controllers/nodes_controller.rb +39 -21
- data/app/controllers/users_controller.rb +8 -3
- data/app/controllers/versions_controller.rb +2 -2
- data/app/controllers/virtual_classes_controller.rb +17 -11
- data/app/helpers/documents_helper.rb +0 -3
- data/app/helpers/users_helper.rb +17 -0
- data/app/models/cache.rb +36 -31
- data/app/models/column.rb +48 -5
- data/app/models/comment.rb +14 -5
- data/app/models/data_entry.rb +2 -2
- data/app/models/document.rb +23 -33
- data/app/models/idx_nodes_datetime.rb +4 -0
- data/app/models/idx_nodes_float.rb +4 -0
- data/app/models/idx_project.rb +3 -0
- data/app/models/node.rb +372 -308
- data/app/models/page.rb +1 -31
- data/app/models/relation.rb +4 -4
- data/app/models/relation_proxy.rb +128 -17
- data/app/models/role.rb +27 -2
- data/app/models/site.rb +64 -56
- data/app/models/template.rb +11 -12
- data/app/models/text_document.rb +6 -7
- data/app/models/user.rb +95 -46
- data/app/models/version.rb +2 -2
- data/app/models/virtual_class.rb +418 -73
- data/app/views/columns/_form.html.erb +1 -1
- data/app/views/columns/_li.html.erb +1 -1
- data/app/views/comments/_form.rhtml +1 -1
- data/app/views/comments/_li.rhtml +1 -1
- data/app/views/comments/_li_simple.rhtml +1 -1
- data/app/views/groups/_form.rhtml +1 -1
- data/app/views/links/_li.rhtml +1 -1
- data/app/views/nodes/_groups.rhtml +1 -1
- data/app/views/nodes/_import_results.rhtml +1 -1
- data/app/views/nodes/_parent.rhtml +1 -1
- data/app/views/nodes/_results.rhtml +1 -1
- data/app/views/nodes/create.rjs +4 -2
- data/app/views/relations/_li.erb +2 -2
- data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
- data/app/views/templates/document_create_tabs/_template.rhtml +2 -2
- data/app/views/templates/document_create_tabs/_text_document.rhtml +2 -2
- data/app/views/templates/edit_tabs/_help.rhtml +1 -1
- data/app/views/templates/edit_tabs/_title.rhtml +0 -3
- data/app/views/users/_form.rhtml +2 -6
- data/app/views/users/_li.rhtml +1 -3
- data/app/views/users/create.rjs +4 -4
- data/app/views/users/preferences.html.erb +1 -4
- data/app/views/versions/custom_tab.rhtml +5 -0
- data/app/views/virtual_classes/_form.erb +20 -10
- data/app/views/virtual_classes/_li.erb +21 -8
- data/app/views/zafu/default/Node-+search.zafu +1 -1
- data/app/views/zafu/default/Node.zafu +3 -3
- data/bricks/captcha/lib/bricks/captcha.rb +1 -1
- data/bricks/mongrel/zena/deploy.rb +14 -0
- data/bricks/{data2pdf → pdf}/.document +0 -0
- data/bricks/pdf/README +33 -0
- data/bricks/{data2pdf → pdf}/Rakefile +0 -0
- data/bricks/pdf/VERSION +1 -0
- data/bricks/pdf/lib/bricks/pdf.rb +110 -0
- data/bricks/pdf/lib/bricks/pdf/engine/prince.rb +38 -0
- data/bricks/pdf/lib/bricks/pdf/engine/xhtml2pdf.rb +9 -0
- data/bricks/pdf/lib/bricks/pdf/install.rb +121 -0
- data/bricks/pdf/test/engines/test_prince.rb +15 -0
- data/bricks/pdf/test/engines/test_xhtml2pdf.rb +15 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/application.css +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/contact.html +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/pisa-default.css +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/sheet1.css +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/sheet2.css +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/simple-html.html +0 -0
- data/bricks/{data2pdf → pdf}/test/fixtures/simple-text.txt +0 -0
- data/bricks/{data2pdf → pdf}/test/helper.rb +4 -5
- data/bricks/pdf/test/shoulda_macros/shoulda_pdf.rb +72 -0
- data/bricks/pdf/zena/init.rb +5 -0
- data/bricks/pdf/zena/tasks.rb +17 -0
- data/bricks/sphinx/lib/{use_sphinx.rb → bricks/sphinx.rb} +1 -1
- data/bricks/tags/zena/init.rb +2 -2
- data/bricks/tags/zena/test/zafu/tags.yml +4 -4
- data/bricks/zena/zena/migrate/01_base.rb +482 -0
- data/config/bricks.yml +22 -6
- data/config/gems.yml +8 -6
- data/db/20100628074512_zena0x_to1x.rb +6 -1
- data/db/fix/024_correct_vclass_kpath.rb +11 -0
- data/db/fix/025_move_tag_into_vclass.rb +13 -0
- data/db/{migrate → fix}/026_rename_templates.rb +0 -0
- data/db/{migrate → fix}/045_avoid_star_in_templates.rb +0 -0
- data/db/{migrate → fix}/046_fix_zazen_image_tag.rb +0 -0
- data/db/{migrate → fix}/047_change_default_link_id_to_zero.rb +1 -3
- data/db/{migrate → fix}/049_fix_publish_from_is_null.rb +0 -0
- data/db/{migrate → fix}/20090924141459_zafu_fix_sept09.rb +0 -0
- data/db/{migrate → fix}/20091013100351_rename_publish_group_to_drive_group.rb +1 -3
- data/db/{migrate → fix}/20091124161608_rebuild_fullpath.rb +0 -1
- data/db/{migrate → fix}/20100115134729_rebuild_fullpath_after_fix.rb +0 -0
- data/db/{migrate → fix}/20100526090140_renamed_contact_model_to_base_contact.rb +2 -4
- data/db/{migrate → fix/old_migrations}/001_create_base.rb +0 -1
- data/db/{migrate → fix/old_migrations}/002_add_time_zone_to_users.rb +0 -0
- data/db/{migrate → fix/old_migrations}/003_add_custom_base_flag.rb +0 -0
- data/db/{migrate → fix/old_migrations}/004_rename_template_skin.rb +0 -0
- data/db/{migrate → fix/old_migrations}/005_create_cached_pages.rb +0 -0
- data/db/{migrate → fix/old_migrations}/006_create_sites.rb +0 -0
- data/db/{migrate → fix/old_migrations}/007_replace_id_by_zip.rb +0 -0
- data/db/{migrate → fix/old_migrations}/008_user_status.rb +0 -0
- data/db/{migrate → fix/old_migrations}/009_fulltext.rb +0 -0
- data/db/fix/old_migrations/010_create_template_content.rb +17 -0
- data/db/{migrate → fix/old_migrations}/011_project_to_section.rb +0 -0
- data/db/{migrate → fix/old_migrations}/012_add_project_id.rb +0 -0
- data/db/{migrate → fix/old_migrations}/013_remove_defaults.rb +0 -0
- data/db/{migrate → fix/old_migrations}/014_add_sort_field.rb +0 -0
- data/db/{migrate → fix/old_migrations}/015_add_dyn_attributes.rb +0 -0
- data/db/{migrate → fix/old_migrations}/016_remove_translations.rb +0 -0
- data/db/{migrate → fix/old_migrations}/017_rename_authorize.rb +0 -0
- data/db/{migrate → fix/old_migrations}/018_add_auth_option.rb +0 -0
- data/db/{migrate → fix/old_migrations}/019_remove_user_status.rb +0 -0
- data/db/{migrate → fix/old_migrations}/020_create_participation.rb +0 -0
- data/db/{migrate → fix/old_migrations}/021_create_relations.rb +0 -0
- data/db/{migrate → fix/old_migrations}/022_create_virtual_classes.rb +0 -0
- data/db/{migrate → fix/old_migrations}/023_ip_on_anonymous_comment.rb +0 -0
- data/db/{migrate → fix/old_migrations}/027_add_country_to_contacts.rb +0 -0
- data/db/{migrate → fix/old_migrations}/028_change_size_of_conten_type_field.rb +0 -0
- data/db/{migrate → fix/old_migrations}/029_create_data_entries.rb +0 -0
- data/db/{migrate → fix/old_migrations}/030_redit_auto_publish_site_settings.rb +0 -0
- data/db/{migrate → fix/old_migrations}/031_create_iformats.rb +0 -0
- data/db/{migrate → fix/old_migrations}/032_caches_context_as_hash.rb +0 -0
- data/db/{migrate → fix/old_migrations}/033_documents_kpath_change.rb +0 -0
- data/db/{migrate → fix/old_migrations}/034_change_file_storage.rb +0 -0
- data/db/{migrate → fix/old_migrations}/035_add_status_to_link.rb +0 -0
- data/db/{migrate → fix/old_migrations}/036_add_flag_fields_on_nodes.rb +0 -0
- data/db/{migrate → fix/old_migrations}/037_add_auto_create_discussion_to_v_class.rb +0 -0
- data/db/{migrate → fix/old_migrations}/038_create_site_attributes.rb +0 -0
- data/db/{migrate → fix/old_migrations}/039_default_position.rb +0 -0
- data/db/{migrate → fix/old_migrations}/040_second_value_for_data_entry.rb +0 -0
- data/db/{migrate → fix/old_migrations}/041_add_attributes_to_v_class.rb +0 -0
- data/db/{migrate → fix/old_migrations}/042_fix_position_should_be_float.rb +0 -0
- data/db/{migrate → fix/old_migrations}/043_move_user_lang_into_participation.rb +0 -0
- data/db/{migrate → fix/old_migrations}/044_remove_monolingual_site_option.rb +0 -0
- data/db/{migrate → fix/old_migrations}/048_link_source_target_can_be_null.rb +0 -0
- data/db/{migrate → fix/old_migrations}/050_date_in_links.rb +0 -0
- data/db/{migrate → fix/old_migrations}/051_add_exif_tags_to_images.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20090825201159_insert_zero_link.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20090825201200_merge_bricks_migrations_with_std_migrations.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20090927125912_allow_null_in_text_fields.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20090928133440_no_more_private_nodes.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20090928143754_version_status_change.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091001084025_change_status_values_for_comments.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091009084057_add_vhash_in_node.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091014130833_fix_template_title.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091014183726_merge_participation_into_users.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091018200734_add_popup_info_to_image_format.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091026161708_add_persistence_token.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091101184952_add_session_table.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20091123175137_add_single_access_token.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100125062254_add_dynamo_to_version.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100201133242_remove_default_status_on_version.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100208194210_create_attachments.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100210112319_change_dynamo_to_property.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100320145726_transform_template_contents_into_index.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100328125634_change_skin_name_to_id.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100417061257_add_properties_to_sites.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100419163149_rename_name_to_node_name.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100422091606_change_v_class_table_into_roles.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100422094048_node_habtm_roles.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100422115935_create_columns.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100513181529_add_site_id_to_columns.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100519091711_add_index_definition_to_columns.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100519091940_create_idx_nodes_string.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100519232432_create_idx_nodes_ml_string.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100525113858_add_porperties_to_users.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100527130937_change_column_index_to_string.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100531135128_add_fulltext_builder_fields.rb +0 -0
- data/db/{migrate → fix/old_migrations}/20100915062903_add_api_group_id_to_site.rb +0 -0
- data/db/fix/old_migrations/20100923154807_remove_base_contact.rb +84 -0
- data/db/fix/old_migrations/20100926192223_remove_su_user.rb +8 -0
- data/db/fix/old_migrations/20100927141658_add_eval_attributes_to_v_class.rb +12 -0
- data/db/fix/old_migrations/20100928185257_add_obvious_idx.rb +52 -0
- data/db/fix/old_migrations/20100929143111_remove_node_name.rb +11 -0
- data/db/fix/old_migrations/20101006090454_store_properties_in_long_text.rb +9 -0
- data/db/fix/old_migrations/20101014185753_remove_user_prototype_id.rb +9 -0
- data/db/fix/old_migrations/20101101084318_create_scope_index.rb +35 -0
- data/db/fix/old_migrations/20101109074232_create_idx_nodes_tables.rb +65 -0
- data/db/fix/old_migrations/20101110184235_add_role_update_to_site.rb +9 -0
- data/db/fix/old_migrations/20101116103920_change_scope_index.rb +31 -0
- data/db/fix/old_migrations/20101123125822_add_integer_idx.rb +17 -0
- data/db/fix/old_migrations/20101130134522_add_index_field.rb +13 -0
- data/db/fix/old_migrations/20101213133816_add_group_to_relation.rb +9 -0
- data/db/init/base/help.fr.zml +1 -1
- data/db/init/base/skins/default.zml +0 -1
- data/db/init/base/skins/default/Node-+search.zafu +1 -1
- data/db/init/base/skins/default/Node-tree.zafu +3 -3
- data/db/init/base/skins/default/Node.zafu +3 -3
- data/lib/bricks/loader.rb +4 -1
- data/lib/bricks/requirements_validation.rb +11 -6
- data/lib/log_recorder/lib/log_recorder.rb +2 -2
- data/lib/tasks/zena.rake +25 -15
- data/lib/zena.rb +42 -9
- data/lib/zena/acts/enrollable.rb +81 -99
- data/lib/zena/acts/secure.rb +27 -23
- data/lib/zena/acts/secure_node.rb +10 -55
- data/lib/zena/acts/serializable.rb +9 -10
- data/lib/zena/app.rb +0 -2
- data/lib/zena/code_syntax.rb +1 -1
- data/lib/zena/controller/test_case.rb +0 -5
- data/lib/zena/core_ext/string.rb +48 -20
- data/lib/zena/db.rb +10 -442
- data/lib/zena/db_helper/abstract_db.rb +184 -0
- data/lib/zena/db_helper/mysql.rb +150 -0
- data/lib/zena/db_helper/postgresql.rb +79 -0
- data/lib/zena/db_helper/sqlite3.rb +135 -0
- data/lib/zena/deploy.rb +4 -1
- data/lib/zena/deploy/httpd.rhtml +3 -3
- data/lib/zena/deploy/vhost.rhtml +1 -1
- data/lib/zena/foxy_parser.rb +37 -18
- data/lib/zena/info.rb +3 -13
- data/lib/zena/migrator.rb +0 -1
- data/lib/zena/parser/zafu_rules.rb +9 -4
- data/lib/zena/parser/zazen_rules.rb +5 -5
- data/lib/zena/parser/zena_rules.rb +1 -1
- data/lib/zena/remote/interface.rb +1 -1
- data/lib/zena/site_worker.rb +3 -3
- data/lib/zena/test_controller.rb +10 -10
- data/lib/zena/use/action.rb +66 -6
- data/lib/zena/use/ajax.rb +39 -13
- data/lib/zena/use/ancestry.rb +210 -0
- data/lib/zena/use/authlogic.rb +30 -1
- data/lib/zena/use/calendar.rb +158 -0
- data/lib/zena/use/conditional.rb +3 -2
- data/lib/zena/use/context.rb +42 -12
- data/lib/zena/use/dates.rb +15 -14
- data/lib/zena/use/display.rb +54 -7
- data/lib/zena/use/error_rendering.rb +1 -0
- data/lib/zena/use/field_index.rb +20 -0
- data/lib/zena/use/fixtures.rb +12 -9
- data/lib/zena/use/forms.rb +230 -106
- data/lib/zena/use/fulltext.rb +28 -14
- data/lib/zena/use/html_tags.rb +1 -24
- data/lib/zena/use/i18n.rb +69 -14
- data/lib/zena/use/kpath.rb +60 -0
- data/lib/zena/use/ml_index.rb +6 -4
- data/lib/zena/use/node_context.rb +63 -0
- data/lib/zena/use/prop_eval.rb +90 -0
- data/lib/zena/use/query_builder.rb +159 -29
- data/lib/zena/use/query_comment.rb +1 -1
- data/lib/zena/use/query_node.rb +147 -56
- data/lib/zena/use/recursion.rb +2 -2
- data/lib/zena/use/relations.rb +31 -19
- data/lib/zena/use/rendering.rb +111 -121
- data/lib/zena/use/scope_index.rb +230 -0
- data/lib/zena/use/search.rb +7 -7
- data/lib/zena/use/urls.rb +87 -25
- data/lib/zena/use/version_hash.rb +113 -113
- data/lib/zena/use/workflow.rb +5 -1
- data/lib/zena/use/zafu_attributes.rb +11 -14
- data/lib/zena/use/zafu_eval.rb +1 -1
- data/lib/zena/use/zafu_safe_definitions.rb +91 -9
- data/lib/zena/use/zafu_templates.rb +146 -102
- data/lib/zena/use/zazen.rb +5 -4
- data/lib/zena/zafu_compiler.rb +1 -0
- data/locale/en/LC_MESSAGES/zena.mo +0 -0
- data/locale/en/zena.po +0 -1
- data/locale/fr/LC_MESSAGES/zena.mo +0 -0
- data/locale/fr/zena.mo +0 -0
- data/locale/fr/zena.po +4 -4
- data/misc/zena +35 -0
- data/misc/zena_init +41 -0
- data/public/images/ext/{basecontact.png → contact.png} +0 -0
- data/public/javascripts/zena.js +35 -7
- data/public/stylesheets/admin.css +5 -2
- data/public/stylesheets/default.css +2 -1
- data/public/stylesheets/popup.css +1 -1
- data/public/stylesheets/zena.css +2 -2
- data/test/custom_queries/complex.host.yml +12 -5
- data/test/fixtures/files/Node-test.zafu +3 -3
- data/test/fixtures/files/translations_fr.yml +4 -2
- data/test/functional/documents_controller_test.rb +31 -0
- data/test/functional/nodes_controller_commit_test.rb +1 -5
- data/test/functional/nodes_controller_test.rb +92 -12
- data/test/functional/user_sessions_controller_test.rb +2 -2
- data/test/functional/users_controller_test.rb +31 -29
- data/test/functional/versions_controller_test.rb +2 -2
- data/test/functional/virtual_classes_controller_test.rb +2 -2
- data/test/integration/multiple_hosts_test.rb +19 -8
- data/test/integration/navigation_test.rb +91 -12
- data/test/integration/query_node/basic.yml +40 -37
- data/test/integration/query_node/complex.yml +23 -18
- data/test/integration/query_node/dates.yml +3 -3
- data/test/integration/query_node/errors.yml +7 -1
- data/test/integration/query_node/filters.yml +41 -35
- data/test/integration/query_node/idx_fields.yml +11 -0
- data/test/integration/query_node/idx_key_value.yml +77 -0
- data/test/integration/query_node/idx_scope.yml +33 -0
- data/test/integration/query_node/relations.yml +13 -13
- data/test/integration/query_node_test.rb +6 -10
- data/test/integration/zafu_compiler/action.yml +19 -6
- data/test/integration/zafu_compiler/ajax.yml +111 -51
- data/test/integration/zafu_compiler/apphelper.yml +1 -1
- data/test/integration/zafu_compiler/asset.yml +1 -1
- data/test/integration/zafu_compiler/basic.yml +42 -52
- data/test/integration/zafu_compiler/calendar.yml +3 -3
- data/test/integration/zafu_compiler/complex.yml +16 -16
- data/test/integration/zafu_compiler/complex_ok.yml +2 -2
- data/test/integration/zafu_compiler/conditional.yml +42 -33
- data/test/integration/zafu_compiler/data.yml +3 -3
- data/test/integration/zafu_compiler/dates.yml +25 -10
- data/test/integration/zafu_compiler/display.yml +49 -12
- data/test/integration/zafu_compiler/errors.yml +26 -6
- data/test/integration/zafu_compiler/eval.yml +4 -4
- data/test/integration/zafu_compiler/forms.yml +89 -15
- data/test/integration/zafu_compiler/i18n.yml +23 -18
- data/test/integration/zafu_compiler/idx_scope.yml +7 -0
- data/test/integration/zafu_compiler/later.yml +10 -16
- data/test/integration/zafu_compiler/off/off.yml +2 -2
- data/test/integration/zafu_compiler/query.yml +207 -0
- data/test/integration/zafu_compiler/recursion.yml +2 -2
- data/test/integration/zafu_compiler/relations.yml +144 -168
- data/test/integration/zafu_compiler/roles.yml +86 -10
- data/test/integration/zafu_compiler/rubyless.yml +49 -6
- data/test/integration/zafu_compiler/safe_definitions.yml +35 -6
- data/test/integration/zafu_compiler/search.yml +1 -1
- data/test/integration/zafu_compiler/security.yml +37 -0
- data/test/integration/zafu_compiler/urls.yml +50 -40
- data/test/integration/zafu_compiler/user.yml +21 -6
- data/test/integration/zafu_compiler/version.yml +6 -6
- data/test/integration/zafu_compiler/zafu_attributes.yml +43 -34
- data/test/integration/zafu_compiler/zazen.yml +10 -10
- data/test/integration/zafu_compiler_test.rb +19 -13
- data/test/sites/complex/nodes.yml +0 -2
- data/test/sites/complex/roles.yml +9 -1
- data/test/sites/complex/sites.yml +0 -1
- data/test/sites/complex/users.yml +2 -5
- data/test/sites/ocean/nodes.yml +2 -5
- data/test/sites/ocean/roles.yml +8 -0
- data/test/sites/ocean/sites.yml +0 -1
- data/test/sites/ocean/users.yml +0 -13
- data/test/sites/zena/columns.yml +27 -5
- data/test/sites/zena/idx_projects.yml +5 -0
- data/test/sites/zena/nodes.yml +8 -32
- data/test/sites/zena/relations.yml +5 -0
- data/test/sites/zena/roles.yml +25 -3
- data/test/sites/zena/sites.yml +2 -2
- data/test/sites/zena/users.yml +1 -21
- data/test/sites/zena/versions.yml +35 -12
- data/test/test_helper.rb +7 -0
- data/test/unit/after_commit_test.rb +7 -7
- data/test/unit/cache_test.rb +32 -0
- data/test/unit/cached_page_test.rb +1 -1
- data/test/unit/column_test.rb +31 -7
- data/test/unit/comment_test.rb +2 -2
- data/test/unit/core_ext_test.rb +38 -7
- data/test/unit/document_test.rb +14 -42
- data/test/unit/node_test.rb +311 -324
- data/test/unit/note_test.rb +23 -31
- data/test/unit/page_test.rb +16 -58
- data/test/unit/project_test.rb +2 -2
- data/test/unit/relation_proxy_test.rb +148 -21
- data/test/unit/relation_test.rb +23 -3
- data/test/unit/remote_test.rb +15 -9
- data/test/unit/role_test.rb +9 -0
- data/test/unit/site_test.rb +49 -47
- data/test/unit/skin_test.rb +16 -0
- data/test/unit/template_test.rb +60 -69
- data/test/unit/text_document_test.rb +15 -14
- data/test/unit/user_test.rb +101 -41
- data/test/unit/version_test.rb +4 -4
- data/test/unit/virtual_class_test.rb +577 -36
- data/test/unit/workflow_test.rb +58 -21
- data/test/unit/zena/acts/enrollable_test.rb +36 -127
- data/test/unit/zena/acts/secure_test.rb +6 -22
- data/test/unit/zena/acts/serializable_test.rb +18 -0
- data/test/unit/zena/db_test.rb +14 -14
- data/test/unit/zena/parser/zafu.yml +5 -3
- data/test/unit/zena/use/ancestry_test.rb +198 -0
- data/test/unit/zena/use/calendar_test.rb +8 -8
- data/test/unit/zena/use/dates_test.rb +2 -0
- data/test/unit/zena/use/fulltext_test.rb +9 -1
- data/test/unit/zena/use/html_tags_test.rb +2 -16
- data/test/unit/zena/use/i18n_test.rb +2 -2
- data/test/unit/zena/use/kpath_test.rb +13 -0
- data/test/unit/zena/use/ml_index_test.rb +60 -12
- data/test/unit/zena/use/prop_eval_test.rb +170 -0
- data/test/unit/zena/use/query_node_test.rb +9 -2
- data/test/unit/zena/use/rendering_test.rb +98 -1
- data/test/unit/zena/use/scope_index_test.rb +464 -0
- data/test/unit/zena/use/urls_test.rb +23 -13
- data/test/unit/zena/use/version_hash_test.rb +2 -2
- data/test/unit/zena/use/zafu_template_test.rb +21 -8
- data/test/unit/zena/use/zazen_test.rb +47 -47
- data/zena.gemspec +177 -143
- metadata +222 -141
- data/app/models/base_contact.rb +0 -79
- data/app/models/book.rb +0 -242
- data/app/models/contact_content.rb +0 -70
- data/app/models/contact_version.rb +0 -40
- data/app/models/reference.rb +0 -18
- data/app/views/templates/edit_tabs/_basecontact.rhtml +0 -8
- data/bricks/data2pdf/README +0 -19
- data/bricks/data2pdf/VERSION +0 -1
- data/bricks/data2pdf/lib/data2pdf.rb +0 -60
- data/bricks/data2pdf/lib/engines/prince.rb +0 -39
- data/bricks/data2pdf/lib/engines/xhtml2pdf.rb +0 -41
- data/bricks/data2pdf/lib/install.rb +0 -111
- data/bricks/data2pdf/test/engines/test_prince.rb +0 -14
- data/bricks/data2pdf/test/engines/test_xhtml2pdf.rb +0 -14
- data/bricks/data2pdf/test/shoulda_macros/shoulda_data2pdf.rb +0 -91
- data/bricks/data2pdf/test/unit/test_rendering.rb +0 -37
- data/config/routes.rb +0 -3
- data/db/migrate/010_create_template_content.rb +0 -17
- data/db/migrate/024_correct_vclass_kpath.rb +0 -13
- data/db/migrate/025_move_tag_into_vclass.rb +0 -15
- data/lib/version_off.rb +0 -323
- data/lib/zena/use/node_name.rb +0 -94
- data/test/integration/query_node/properties.yml +0 -41
- data/test/unit/base_contact_test.rb +0 -242
- 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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
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') }
|
22
|
-
|
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
|
data/app/models/comment.rb
CHANGED
@@ -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,
|
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 ||=
|
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] || (
|
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
|
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
|
data/app/models/data_entry.rb
CHANGED
@@ -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 => '
|
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.
|
87
|
+
user.node
|
88
88
|
end
|
89
89
|
|
90
90
|
def nodes
|
data/app/models/document.rb
CHANGED
@@ -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
|
50
|
-
validate
|
51
|
-
|
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
|
-
|
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
|
85
|
-
|
85
|
+
if real_class != self
|
86
|
+
secure(real_class) { real_class.o_new(attrs, vclass) }
|
86
87
|
else
|
87
|
-
|
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 =
|
228
|
-
|
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
|
237
|
-
|
238
|
-
|
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
|
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
|
-
|
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
|
-
===
|
32
|
+
=== Properties
|
56
33
|
|
57
|
-
|
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
|
-
|
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::
|
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'
|
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 =>
|
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
|
-
|
147
|
-
|
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
|
-
#
|
195
|
-
|
196
|
-
|
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
|
-
|
199
|
-
|
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
|
-
|
208
|
-
safe_method
|
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
|
-
:
|
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::
|
225
|
-
include Zena::Use::VersionHash
|
252
|
+
include Zena::Use::Ancestry::ModelMethods
|
226
253
|
|
227
254
|
# to_xml
|
228
|
-
include Zena::Acts::Serializable::
|
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
|
-
|
307
|
+
class << self
|
308
|
+
def new(hash={}, vclass = nil)
|
272
309
|
node = super()
|
273
|
-
# set
|
274
|
-
node.
|
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,
|
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
|
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
|
-
#
|
407
|
+
# TODO: remove and use VirtualClass[...].classes_for_form directly
|
322
408
|
def classes_for_form(opts={})
|
323
|
-
|
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
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
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\(([^\)]
|
467
|
+
elsif path = str[/\A\(([^\)]+)\)\Z/,1]
|
412
468
|
if path[0..0] == '/'
|
413
|
-
|
469
|
+
path = path[1..-1].split('/').map {|p| String.from_filename(p) }
|
470
|
+
find_by_path(path)
|
414
471
|
elsif base_node
|
415
|
-
|
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
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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 =
|
449
|
-
|
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(
|
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
|
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
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
493
|
-
|
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 =
|
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 =~ /^([\._~]
|
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
|
-
|
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
|
-
|
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['
|
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
|
-
|
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] =~ /^#{
|
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['
|
571
|
-
attrs['v_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['
|
581
|
-
attrs['v_lang']
|
582
|
-
attrs['class']
|
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['
|
587
|
-
attrs['v_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['
|
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['
|
603
|
-
attrs['ext']
|
604
|
-
attrs['
|
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['
|
830
|
+
res['parent_zip'] = p
|
744
831
|
end
|
745
832
|
|
746
833
|
attributes.each do |key, value|
|
747
|
-
next if ['
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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.
|
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
|
-
|
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(
|
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) &&
|
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
|
1383
|
+
elsif text.blank? && klass == 'Page' && children
|
1348
1384
|
# skip zml
|
1349
1385
|
else
|
1350
|
-
File.open(File.join(path,
|
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,
|
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
|
-
|
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
|
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 = [
|
1791
|
+
if !new_record?
|
1792
|
+
cond = ['nodes.id != ?', id]
|
1725
1793
|
else
|
1726
|
-
cond =
|
1794
|
+
cond = nil
|
1727
1795
|
end
|
1728
1796
|
|
1729
|
-
if taken_name = Node.
|
1730
|
-
if taken_name =~ /^#{
|
1731
|
-
self
|
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
|
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
|