diff --git a/.classpath b/.classpath index f252e014d6..f0238d31b2 100644 --- a/.classpath +++ b/.classpath @@ -9,5 +9,6 @@ + diff --git a/config/alfresco/messages/webclient.properties b/config/alfresco/messages/webclient.properties index 9fccc43fe4..08c59a8289 100644 --- a/config/alfresco/messages/webclient.properties +++ b/config/alfresco/messages/webclient.properties @@ -1,1288 +1,1314 @@ -# I18N message properties - -# Date Pattern -date_pattern=d MMMM yyyy -date_time_pattern=d MMMM yyyy HH:mm -time_pattern=HH:mm - -# General UI -product_name=Alfresco -view_description=This view allows you to browse the items in this space. -search_description=This view allows you to see the results from your search. -checkinfile_description=Check in your working copy for other team members to work with. -checkoutfilelink_description=Edit the checked out file, undo the check out or carry on working. -checkoutfile_description=Enter information about the check out. -documentdetails_description=View the details about the content. -linkdetails_description=View the details about the link object. -previewdocument_description=Preview the content or space within a Template. -spacedetails_description=View the details about the space. -undocheckoutfile_description=Cancel the check out of a document and discard any changes. -updatefile_description=Update a document on the repository with content from your computer. -editfile_description=Edit the content of the file. -editfileinline_description=Edit the content of the document, then click Save. -createfile_description=Enter new content. -manageusers_description=Manage the users of the repository. -manage_invited_users_description=Manage the permissions you have granted to users who access your space. -manage_content_users_description=Manage the permissions you have granted to users who access your content. -modify_user_roles_description=Modify the permissions granted to a user for accessing your space. -modify_content_user_roles_description=Modify the permissions granted to a user for accessing your content. -advancedsearch_description=Perform a more detailed search of the repository. -edit_content_description=Modify the content properties then click OK. -view_content_description=View the content properties. -editcategory_description=Set the category for the document then click OK. -editcategory_space_description=Set the category for the space then click OK. -editworkflow_description=Modify the simple workflow properties then click OK. -editspace_description=Modify the space properties then click OK. -editlink_description=Modify the link object properties then click OK. -create_space_description=Enter information about the new space then click Create Space. -space_rules_description=This view shows you all the rules to be applied to content in this space. -warning_inline=This is only recommended for HTML or plain text documents. -categories_description=This view allows you to browse and modify the categories hiearchy. -new_category_description=Enter information about the new Category then click Create Category. -status_message_default=No messages. -no_icons_found=No icons found - -# UI Component messages -yes=Yes -no=No -kilobyte=KB -megabyte=MB -gigabyte=GB -locked_you=Item locked by you -locked_user=Item locked by user -wizard_errors=Please correct the errors below. -last_page=Last Page -next_page=Next Page -prev_page=Previous Page -first_page=First Page -page_info=Page {0} of {1} -go_up=Go Up -ok=OK -go=Go -to=To -from=From -options=Options -other_options=Other Options -local=Local -inherited=Inherited -search=Search -advanced_search=Advanced Search -value_not_set=not set -clear=Clear Results -results_contains=Results for ''{0}''. -results_contains_filter=Results for ''{0}'' in ''{1}''. -details_view=Details View -view_icon=Icon View -view_browse=Browse View -create_options=Create -more_options=More... -more_actions=More Actions -more_options_space=More Actions for this Space -more_options_file=More Actions for this Document -select_space_prompt=Click here to select a Space -select_existing_space_prompt=Click here to select an existing Space -select_home_space_prompt=Click here to select the Home Space location -select_category_prompt=Click here to select a Category -select_destination_prompt=Click here to select the destination -add_new=Add New -change=Change -set=Set -no_categories_applied=This document does not yet have any categories applied. -has_following_categories=This document has the following categories applied... -no_categories_applied_space=This space does not yet have any categories applied. -has_following_categories_space=This space has the following categories applied... -moved=moved -copied=copied -document_action=The document will be {0} to ''{1}'' if the ''{2}'' action is taken. -clipboard=Clipboard -recent_spaces=Recent Spaces -shortcuts=Shortcuts -company_home=Company Home -my_home=My Home -guest_home=Guest Home -new_search=New Search -search_results=Search Results -search_detail=Search for \"{0}\" results shown below -close_search=Close Search -browse_spaces=Browse Spaces -browse_content=Content Items -location=Location -toggle_shelf=Hide or Show the Shelf -shelf=Shelf -actions=Actions -view=View -view_details=View Details -view_details_file=View Details for file -change_details=Change Details -update=Update -cut=Cut -copy=Copy -paste=Paste -remove=Remove -change_roles=Change Roles -change_user_roles=Change User Roles -paste_item=Paste Item -paste_link=Paste As Link -paste_all=Paste All -remove_item=Remove Item -remove_all=Remove All -close=Close -invite=Invite -invite_user=Invite User -filter_contents=Filter Contents -users=Users -groups=Groups -resetall=Reset All -content_rules=Content Rules -view_in_browser=View In Browser -view_in_webdav=View in WebDAV -view_in_cifs=View in CIFS -download_content=Download Content -details_page_bookmark=External Access URL -noderef_link=Alfresco Node Reference -links=Links -create_shortcut=Create Shortcut -navigation=Navigation -next_item=Next Item -previous_item=Previous Item -cancel=Cancel -upload=Upload -homespace=Home Space -network_folder=Open Network Folder -other_action=Run Action -information=Information -move=Move -type=Type -aspect=Aspect -workflow=Workflow -rules=Rules -system_error=System Error -login=Login -templates=Templates -template=Template -rss_template=RSS Template -select_button=Select... -select_items=Select items -select_an_item=Select an item -selected_items=Selected Items -add_to_list_button=Add to List -none=None -no_selected_items=No selected items. -search_select_item=Search for and select an item. -search_select_items=Search for and select items. -search_minimum=Not enough information was entered to perform a search, at least one value must be entered or a location selected to search within. Text fields require a minimum of {0} characters. -filter=Filter -choose_icon=Choose icon -security=Security -all_formats=All Formats -rules_count=Number of rules applied to this Space -working_copy_document=Working Copy -copy_of=Copy of -link_to=Link to -icon=Icon -lock=Lock -unlock=Unlock -items_per_page=Items Per Page -raise_issue=Raise an Issue -click_to_set_date=Click to set a date -today=Today -reset=Reset - -# Properties -username=User Name -joindate=Join Date -roles=Roles -help=Help -name=Name -password=Password -confirm=Confirm -path=Path -description=Description -created=Created -modified=Modified -created_date=Created Date -modified_date=Modified Date -size=Size -title=Title -author=Author -date=Date -mimetype=Format - -# Repo permission display labels -# Note - these come from the server, the english translation is generally the same -Administrator=Administrator -Consumer=Consumer -Read=Consumer -Coordinator=Coordinator -Collaborator=Collaborator -Contributor=Contributor -Editor=Editor -All=All -ContentManager=Content Manager -ContentPublisher=Content Publisher -ContentContributor=Content Contributor -ContentReviewer=Content Reviewer - -# Actions -delete=Delete -edit=Edit -checkin=Check In -checkout=Check Out -checkout_document=Check out this document -undocheckout=Undo Check Out -delete_space=Delete Space -delete_file=Delete File -delete_rule=Delete Rule -delete_user=Delete User -remove_user=Remove User -create_space=Create Space -add_content=Add Content -create_content=Create Content -create_form=Create Form -add_multiple_files=Add Multiple Files -import_directory=Import Directory -advanced_space_wizard=Advanced Space Wizard -create_rule=Create Rule -reapply_rules=Reapply Rules -reapply_rules_success=The rules have been successfully reapplied. -ignore_inherited_rules=Ignore Inherited Rules -include_inherited_rules=Include Inherited Rules -inherited_rules_being_ignored=Inherited rules are being ignored. -manage_rules=Manage Content Rules -manage_users=Manage System Users -manage_groups=Manage User Groups -manage_invited_users=Manage Space Users -manage_content_users=Manage Content Users -modify_user_roles=Modify User Roles for -modify=Modify -logout=Logout -add=Add -node_browser=Node Browser -reset_config=Reset Config Service -save=Save -user_details=User Details -language=Language -export=Export -import=Import -take_ownership=Take Ownership -create_forums=Create Forum Space -create_forum=Create Forum -create_topic=Create Topic -create_post=Create Post -create_reply=Post Reply -delete_forums=Delete Forum Space -delete_forum=Delete Forum -delete_topic=Delete Topic -delete_post=Delete Post -post_to_topic=Post to Topic -reply=Reply -edit_post=Edit Post -reply_to=Reply to -post_reply=Post Reply - -# Login page message -login_details=Enter Login details -login_err_password_length=Password must be between {0} and {1} characters in length. -login_err_password_chars=Password can only contain characters or digits. -login_err_username_length=Username must be between {0} and {1} characters in length. -login_err_username_chars=Username can only contain characters or digits. -loggedout_details=You have been logged out of Alfresco. -relogin=Re-login to Alfresco -no_access=You have no access to Alfresco. -no_cookies=Cookies must be enabled in your browser for the Alfresco Web-Client to function correctly. -user_err_user_name=User ID cannot contain the characters: {0} - -# Browse list messages -no_space_items=No items to display. Click the ''{0}'' action to create a space. -no_content_items=No items to display. To add an existing document click ''{0}'' action. To create an HTML or Plain Text file click ''{1}'' action. - -# Advanced Search messages -look_in=Look in location -look_for=Look for -all_spaces=All Spaces -specify_space=Specify Space -include_child_spaces=Include child spaces -show_results_for=Show me results for -all_items=All Items -file_names_contents=File names and contents -file_names=File names only -space_names=Space names only -show_results_categories=Show me results in the categories -include_sub_categories=Include sub-categories -also_search_results=More search options -additional_options=Additional options -save_new_search=Save New Search -save_edit_search=Save Modified Search -saved_searches=My Saved Search Options -title_save_search=Save Search Query -save_search_description=Save a search query for use again later -search_props=Saved Search Properties -select_saved_search=Select a Saved Search... -saved_search_warning=This operation will attempt to overwrite the existing saved search ''{0}'' -user_searches=Your Searches -global_searches=Public Searches -save_search_global=Save as a public search available to all users. -folder_type=Folder Type - -# Forum messages -forums=Forum Space -forum=Forum -browse_forums=Browse Forum Spaces and Forums -browse_topics=Browse Topics -browse_posts=Browse Posts -forums_info=This view allows you to browse forum spaces and forums. -forum_info=This view allows you to browse topics in this forum. -topic_info=This view allows you to browse posts in this topic. -no_forums=No forum spaces or forums to display. Click the ''Create Forum Space'' action to create a forum space or ''Create Forum'' action to create a forum. -no_topics=No topics to display. Click the ''Create Topic'' action to create a topic. -no_posts=No posts to display. Click the ''Post to Topic'' action to create a post. -topic=Topic -topics=Topics -post=Post -posted=Posted -create_forums_description=Enter information about the new forum space then click Create Forum Space. -create_forum_description=Enter information about the new forum then click Create Forum. -create_topic_description=Enter information about the new topic then click Create Topic. -create_post_description=Enter the content of the message then click Post. -create_reply_description=Enter message text to reply then click Reply. -modify_forums_properties=Modify Forum Space Properties -forums_props=Forum Space Properties -modify_forum_properties=Modify Forum Properties -forum_props=Forum Properties -topic_props=Topic Properties -modify_topic_properties=Modify Topic Properties -create_forums_finish=To create the forum space click Create Forum Space. -create_forum_finish=To create the forum click Create Forum. -create_topic_finish=To create the topic click Create Topic. -create_post_finish=To create the post click Post. -create_reply_finish=To create the reply click Reply. -forums_details_description=View details about the forum space. -forum_details_description=View details about the forum. -topic_details_description=View details about the topic. -bubble_view=Bubble View -replies=Replies -on=On -reply_message=Reply Message -edit_forums_description=Modify the forum space properties then click OK. -edit_forum_description=Modify the forum properties then click OK. -edit_topic_description=Modify the topic properties then click OK. -edit_post_description=Modify the message then click OK. -edit_post_finish=To save the message click Post. -start_discussion=Start Discussion -discuss=View Discussions -discussion_for={0} discussion - -# Common Wizard messages -steps=Steps -summary=Summary -summary_desc=The information you entered is shown below. -default_instruction=To continue click Next. -next_button=Next -back_button=Back -finish_button=Finish -cancel_button=Cancel -clear_button=Clear -you_may_want=You may want to -summary_step_description=Shows a summary of the information entered. - -# Category Management messages -title_categories_list=Categories -add_category=Add Category -edit_category=Edit Category -delete_category=Delete Category -category_icons=Categories -category_details=Details -category_management=Category Management -title_create_category=Create New Category -new_category=New Category -category_props=Category Properties -items=Items -title_delete_category=Delete Category -delete_category_warning=This category has {0} existing document(s) linked to it. -delete_category_info=To remove this category and all it's sub-categories, click Delete. -delete_category_confirm=Are you sure you want to delete category \"{0}\" and all sub-categories? -title_edit_category=Edit Category -edit_category_description=Edit the information for this category. - -# Groups Management messages -title_groups_list=Groups Management -groups_management=Groups Management -groups_description=Manage the members of a group, create new groups or remove existing groups. -new_group=Create Group -new_sub_group=Create Sub-Group -edit_group=Edit Group -delete_group=Delete Group -add_user=Add User -group_icons=Groups -group_details=Details -root_groups=Root Groups -group_filter_children=Children -group_filter_all=All -title_create_group=Create Group -new_group_description=Enter information about the new Group then click Create Group. -group_props=Group Properties -identifier=Identifier -create_group_warning=The Identifier for a Group cannot be changed once it has been set. -title_delete_group=Delete Group -delete_group_warning=This group has {0} sub-group(s) or user(s) attached to it. -delete_group_info=To delete this Group from the system and remove all members from it, click Delete. -delete_group_confirm=Once the group is removed from the system it will no longer be accessable. Are you sure you want to delete Group \"{0}\" and remove all users from it? -title_add_user_group=Add User to Group -add_user_group_description=Add an existing User to a Group -select_users=Select Users to add to this Group -selected_users=Selected Users -groups_err_group_name=Group ID cannot contain the characters: {0} -groups_err_exists=A group ID with the same name already exists, group identifiers must be unique. - -# Invite Users Wizard messages -invite_title=Invite Users Wizard -invite_desc=This wizard helps you to give other users access to your space. -invite_step1_title=Step One - Invite Users -invite_step1_desc=Select the users and roles they will play in this space. -invite_step2_title=Step Two - Notify Users -invite_step2_desc=Notify the selected users. -i_want_to=I want to... -invite_step_1=Invite Users -invite_step_2=Notify Users -invite_all=Invite All users as guests -invite_users=Specify Users/Groups and their roles -specify_usersgroups=Specify Users/Groups -select_usersgroups=Select user/group and their role(s) -select_role=Select role -selected_usersgroups=Selected users/groups and their role(s) -selected_roles=Selected roles -click_add=Click Add -role=Role -send_email=Do you want to send an email to notify the invited users? -subject=Subject -body=Body -automatic_text=Automatic text -invited_to=You have been invited to ''{0}'' by {1}. -invite_role=You will have the role of: {0} -invite_finish_instruction=To close this wizard and apply your changes click Finish. To review or change your selections click Back. -remove_invited_user_info=To remove an invited user from this space, click Yes. -add_role=Add Role -space_owner=User ''{0}'' is the current owner of this space. -users_and_groups=Users and Groups -authority=Username - -# Invite Content Users Wizard messages -invite_content_title=Invite Content Users Wizard -invite_content_desc=This wizard helps you to give other users access to your content. -invite_content_step1_desc=Select the users and roles they will play for this content. -remove_content_user_info=To remove an invited user from this content, click Yes. -content_owner=User ''{0}'' is the current owner of this content. - -# System Users messages -create_user=Create User -change_password=Change Password -title_change_password=Change User Password -change_password_description=Use this view to change an existing user password. -change_password_instructions=Enter the new password for this user. -show_all=Show All - -# Check-in messages -check_in=Check In -checkin_options=Check In options -checkin_changes_info=Check in changes and keep file checked out -workingcopy_location=Working copy location -which_copy_checkin=Which copy do you want to check in? -which_copy_current=Use copy in current space -which_copy_other=Use copy uploaded from my computer -locate_doc_upload=Locate document to upload -file_location=Location -minor_change=Minor Change -major_change=Major Change -notes=Notes - -# Check-out messages -check_out=Check Out -copy_work_with=A copy of the file ''{0}'' will be made for you to work with. -copy_checkin_changes=When you have completed your changes you need to check in the file to allow others to view the changes. -copy_store_prompt=Where do you want to keep the copy of this file? -store_space_current=In the current space -store_space_selected=In the space selected -check_out_of=Check Out of -copy_file_checkedout=A copy of the file ''{0}'' is now checked out to you for editing. -edit_workingcopy_title=Edit the working copy now -edit_workingcopy_info=To edit the working copy of the file, click the link below and if asked select Save. -continue_working_title=Continue working -continue_working_info1=If you do not want to edit the file now click OK to close the page. -continue_working_info2=To edit the file in the future click the Edit action -download_complete=When the download is complete, click OK. -undo_checkout_for=Undo Check Out for -undo_checkout=Undo Check Out -undo_checkout_info=If you undo the check out of a document, the associated working copy will be deleted and all changes to it since the Check Out will be lost. - -# Document and Space details messages -details_of=Details of -preview_of=Preview of -modify_props_of=Modify Properties of -modify_space_properties=Modify Space Properties -modify_content_properties=Modify Content Properties -view_content_properties=View Content Properties -preview=Preview in Template -custom_view=Custom View -view_links=Links -not_inline_editable=This document is not inline editable. -allow_inline_editing=Allow Inline Editing -not_in_workflow=This document is not part of any workflow. -not_in_category=This document is not categorized. -not_in_category_space=This space is not categorized. -not_versioned=This document has no version history. -allow_categorization=Allow Categorization -allow_versioning=Allow Versioning -version_history=Version History -version=Version -document_properties=Document Properties -link_properties=Link Properties -general_properties=General Properties -other_properties=Other Properties -link_destination=Link Destination -link_destination_details=Link Destination Details -no_other_properties=This document does not have any other properties to show. -modify_categories_of=Modify categories of -space_props=Space Properties -choose_space_icon=Choose space icon -create_space_finish=To create your space click Create Space. -select_category=Select a category -selected_categories=Selected categories -no_selected_categories=No categories selected. -success_ownership=Successfully took ownership of the object. -success_unlock=Successfully unlocked the document. -inherit_permissions=Inherit Parent Space Permissions -success_inherit_permissions=Successfully changed Inherit Parent Permissions to 'Yes' -success_not_inherit_permissions=Successfully changed Inherit Parent Permissions to 'No' -apply_template=Apply Template -apply_dashboard_info=Select a template to be applied to the Space as a Custom view. -apply_dashboard_doc_info=Select a template to be applied to the Document as a Custom view. -apply_rss_feed=Apply RSS Feed Template -apply_rss_feed_info=Select a template to be applied to the Space as an RSS feed. -apply_rss_feed_warning1=This Space must be visible to the Guest user for the RSS feed to be publically viewable, you can Invite the Guest user using the -apply_rss_feed_warning2=view. -rss_feed=RSS Feed -rss_feed_link=RSS Feed Link - -# Export messages -export_info=Exports metadata and content from this or all Spaces. -export_from=Export From -package_name=Package Name -all_spaces_root=Complete Repository -current_space=Current Space -include_children=Include Children -include_self=Include this Space -run_export_in_background=Run export in background -export_error_info=If this option is selected the export will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. - -# Import messages -import_info=Imports an Alfresco content package file into the repository. -import_package_description=Alfresco content package -run_import_in_background=Run import in background -import_error_info=If this option is selected the import will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. - -# Edit Content messages -edit_file_title=Edit file -edit_file_prompt=To edit the file ''{0}'', click the link below and if asked select Save. -edit_download_complete=When the download is complete, click Close. -checkout_file_title=Check out file -checkout_you_may_want=You may want to check out this file to lock it and prevent other users from editing it. -checkout_hint1=Hint: When you check out a file a copy is made for you to work with. -checkout_hint2=When you have finished editing the copy you need to check it in to release the lock and allow others to work with the modified document. -checkout_want_to=to prevent the possibility of other users overwriting your changes. -checkout_warn=Note: You will lose any changes already made to this document. -local_copy_location=Local copy location -locate_content_upload=Locate and upload your document to the repository. - -# System Information and admin page messages -system_info=System Information -current_user=Current User -http_app_state=HTTP Application State -http_session_state=HTTP Session State -http_request_state=HTTP Request State -http_request_params=HTTP Request Parameters -http_request_headers=HTTP Request Headers -repository_props=Repository Properties -repository_patch_succeeded=Succeeded -repository_patch_failed=FAILED -system_props=System Properties -hide_details=Hide Details -show_details=Show Details -user_search_info=To find a user search for them using their first name, last name and/or user name. Alternatively to see all users click 'Show All', however, this may take some time if there are a lot of users in the system. -user_change_homespace_info=Selecting a new home space for a user will not remove the existing permissions on the original home space. You may wish to use the Manage Space Users dialog to modify permissions if they are no longer required on the original home space. - -# Content Wizard messages -add_content_dialog_title=Add Content Dialog -add_content_dialog_desc=This dialog helps you to add content to a space. -upload_content=Upload Content -properties=Properties -general=General -file_name=File Name -content_type=Content Type -content_format=Content Format -inline_editable=Inline Editable -locate_content=Locate content to upload -content_location=Location -uploaded_content=Uploaded Content -click_upload=Click upload -file_upload_success=''{0}'' was uploaded successfully. -modify_props_when_page_closes=Modify all properties when this page closes. -modify_props_when_wizard_closes=Modify all properties when this wizard closes. -modify_props_help_text=Rules applied to this content may require you to enter additional information. -content_finish_instruction=To add the content to this space click Finish. To review or change your selections click Back. -create_content_title=Create Content Wizard -create_content_desc=This wizard helps you to create a new document in a space. -create_content_step1_title=Step One - Specify name and select type -create_content_step1_desc=Specify the name and select the type of content you wish to create. -create_content_step2_title=Step Two - Enter Content -create_content_step2_desc=Enter your document content into the repository. -enter_content=Enter Content -select_type=Select Type -content=Content -text_content=Plain Text Content -html_content=HTML Content -xml_content=XML Content -create_form_title=Create Form Wizard -create_form_desc=Create an XML Form definition from XML Schema and Templates -create_form_step1_title=Upload an XML Schema -create_form_step1_desc=Upload an XML Schema -create_form_step2_title=Edit the XML Schema -create_form_step2_desc=This is the generated XForm based on the schema provided. -selected_templateoutputmethods=Selected Template Output Methods -configure_output_methods_step1_desc=Upload template output method and specify the extension to use for its generated assets. -template_output_method_file=Template Output Method File -extension_for_generated_assets=Extension For Generated Assets -file_extension=Extension -schema=Schema -schema_root_tag_name=Root Tag -edit_xml_schema=Edit XML Schema -template_type=Template Type -configure_template_output_methods=Configure Template Output Methods - -# Rule and Action Wizard messages -run_action_title=Run Action Wizard -run_action_desc=This wizard helps you run an action -run_action_step1_title=Step One - Select Actions -run_action_finish_instruction=To execute the action click Finish. To review or change your selections click Back. -create_rule_title=Create Rule Wizard -create_rule_desc=This wizard helps you create a new rule. -create_rule_step1_title=Step One - Select Conditions -create_rule_step2_title=Step Two - Select Actions -create_rule_step3_title=Step Three - Enter Details -create_rule_finish_instruction=To create the rule click Finish. To review or change your selections click Back. -edit_rule_title=Edit Rule Wizard -edit_rule_desc=This wizard helps you modify a rule. -edit_rule_finish_instruction=To update the rule click Finish. To review or change your selections click Back. -select_action=Select Action -select_an_action=Select an action... -select_a_type=Select a type... -action=Action -action_settings=Action Settings -set_action_values=Set action values -select_feature=Select required feature -version_notes=Version Notes -checkout_location=Check out location -destination=Destination -message=Message -category=Category -categories=Categories -change_category=Change Category -approve_flow=Approve Flow -approve_step_name=Name for approve step -move_or_copy=Choose whether you want to move or copy the content and also the location. -reject_flow=Reject Flow -want_reject_step=Do you want to provide a reject step? -reject_step_name=Name for reject step -required_format=Required format -details=Details -select_condition=Select Condition -select_a_condition=Select a condition... -condition=Condition -conditions=Conditions -condition_settings=Condition Settings -set_condition_values=Set condition values -select_checkout_prompt=Click here to select the check out location -condition_contains_desc=Enter the text pattern required, including any wildcards. The file name includes the file type extension when matching. -file_name_pattern=File name pattern -condition_contains_hints=Hints -condition_contains_hints_desc=Use zz* to match any name that begins with zz; use *.txt to match any text file; use *zz* to match any file name that contains zz anywhere including the beginning or end. -apply_to_sub_spaces=Apply rule to sub spaces -run_in_background=Run rule in background -rule_disabled=Disable rule -not=Not -click_set_and_add=Click to set values and add to list -click_add_to_list=Click to add to list -set_and_add_button=Set Values and Add -selected_conditions=Selected Rule Conditions -selected_actions=Selected Rule Actions -condition_has_aspect=Item has aspect ''{0}'' -condition_has_aspect_not=Item does not have aspect ''{0}'' -condition_is_subtype=Item is a subtype of ''{0}'' -condition_is_subtype_not=Item is not a subtype of ''{0}'' -condition_compare_mime_type=Item has a mimetype of ''{0}'' -condition_compare_mime_type_not=Item does not have a mimetype of ''{0}'' -condition_in_category=Item is in category ''{0}'' -condition_in_category_not=Item is not in category ''{0}'' -condition_compare_property_value=Name property matches ''{0}'' -condition_compare_property_value_not=Name property does not match ''{0}'' -action_add_features=Add aspect ''{0}'' -action_remove_features=Remove aspect ''{0}'' -action_specialise_type=Item is specialised to type ''{0}'' -action_simple_workflow={0} item to ''{1}'' if ''{2}'' action is taken. -action_link_category=Link to category ''{0}'' -action_transform=Copies content to ''{0}'' and transforms to ''{1}'' -action_transform_image=Copies image to ''{0}'' and transforms to ''{1}'' using option ''{2}'' -action_copy=Copy to ''{0}'' -action_move=Move to ''{0}'' -action_mail=Send email to ''{0}'' -action_mail_template=Use Template -action_check_in=Check in content as ''{0}'' with comment ''{1}'' -action_check_out=Check out content to ''{0}'' -action_set_property_value=Sets property ''{0}'' -action_import=Imports to ''{0}'' -action_script=Executes script ''{0}'' -action_script_select=Select a script to execute -not_condition_result=Check the item does not match the criteria above -space=Space -import_to=Import To -encoding=Encoding -encoding_utf8=UTF-8 -rule_type=Rule Type -rule_background_info=If this option is selected the rule will execute in the background so the results may not appear immediately. -select_recipients=Search for email recipients by Name or Group -selected_recipients=Selected email recipients -message_recipients=Message Recipients -email_message=Email message -insert_template=Insert Template -discard_template=Discard Template - -# Create Space Wizard messages -create_space_title=Create Space Wizard -create_space_desc=This wizard helps you to create a new space. -create_space_step1_title=Step One - Starting Space -create_space_step1_desc=Choose how you want to create your space. -create_space_step2_title=Step Two - Space Options -create_space_step2_desc=Select space options. -create_space_step3_title=Step Three - Space Details -create_space_step3_desc=Enter information about the space. -create_space_finish_instruction=To close this wizard and create your space click Finish. To review or change your selections click Back. -scratch=Scratch -an_existing_space=An existing space -a_template=A template -creating_from=Creating From -save_as_template=Save As Template -template_name=Template Name -select_a_template=Select a template... -select_a_script=Select a script... -starting_space=Starting Space -space_options=Space Options -space_details=Space Details -how_to_create_space=How do you want to create your space? -from_scratch=From scratch -based_on_existing_space=Based on an existing space -using_a_template=Using a template -existing_space=Existing Space -copy_existing_space=Copy existing space -structure=Structure -structure_contents=Structure and contents -space_copy_note=Note: Any content rules for spaces will also be copied. -space_type=Space Type -space_type_create=Select the type of space you want to create. -container=Folder Space -container_desc=A place for keeping and organizing documents and other spaces. -forums_desc=A place to discuss content with other users. -space_type_note=Note: If you can only see one type of space then other space types may not be enabled. See your System Administrator for further help. -template_space=Template Space -select_template=Select the template you want to use. - -# Create Website Wizard messages -create_website=Create Website -create_website_title=Create Website Wizard -create_website_desc=This wizard helps you create a new website space. -website_details=Website Details -create_website_step1_title=Step One - Website Details -create_website_step1_desc=Enter the information about the website. -website_invite=Invite Users -create_website_step2_title=Step Two - Invite Users -create_website_step2_desc=Select users and their roles. -website_notify=Email Users -create_website_step3_title=Step Three - Notify Users -create_website_step3_desc=Notify the invited users. -create_website_finish_instruction=To close this wizard and create your website space click Finish. To review or change your selections click Back. -create_website_summary_users=Users and Roles - -# Delete Website Dialog messages -delete_website=Delete Website -delete_website_info=To remove this website and all associated user sandboxes, click OK. -delete_website_confirm=Are you sure you want to delete the website \"{0}\" and all associated user sandboxes? - -# Browse Website and Sandboxes messages -title_browse_website=Browse Website -website_info=Use this view to browse the staging area and user sandboxes for a website. -staging_sandbox=Staging Sandbox -user_sandboxes=User Sandboxes -sandbox_preview=Preview Website -sandbox_create=Create New Content -sandbox_browse=Browse Website -sandbox_submitall=Submit All -sandbox_icon=Browse Website -import_website_content=Import Website Content -title_browse_sandbox=Browse Sandbox -sandbox_info=Use this view to browse the files and folders within the sandbox for a website. -sandbox_title=Website ''{0}'' sandbox ''{1}'' -sandbox_staging=Staging -website_browse_folders=Browse Folders -website_browse_files=Browse Files -creator=Creator -modified_items=Modified Items -store_created_on=Created On -store_created_by=Created By -store_working_users=There are {0} user(s) working on this website. -avm_node_deleted=Deleted -submit=Submit -submit_success=Successfully submitted item: {0} -submitall_success=Successfully submitted sandbox for user: {0} -folder_preview=Preview Folder -file_preview=Preview File - -# Website actions and dialog messages -title_import_content=Import Content into Website -import_website_content_title=Import Content into Website -import_website_content_desc=Use this dialog to import an archive of content into the root of the website. -delete_avm_file_info=To remove this file from the sandbox, click OK. -delete_avm_file_confirm=Are you sure you want to remove \"{0}\" from the sandbox? -delete_folder=Delete Folder -delete_avm_folder_info=To remove this folder and its contents from the sandbox, click OK. -delete_avm_folder_confirm=Are you sure you want to remove \"{0}\" and its contents from the sandbox? -error_delete_folder=Unable to delete Folder due to system error: -create_web_content_title=Create Web Content Wizard -create_web_content_desc=This wizard helps you to create a new content item for a website. -create_folder=Create Folder -create_avm_folder_info=Create a new folder in the website. -add_avm_content_dialog_desc=This dialog helps you to add content to a folder. -update_avm_file_desc=Update a file in the website with content from your computer. -file_details_desc=View details about the file. -title_folder_details=Folder Details -folder_details_desc=View details about the folder. -edit_file_properties=Edit File Properties -edit_file_description=Edit the properties of the file then click OK. -edit_folder_properties=Edit Folder Properties -edit_folder_description=Edit the properties of the folder then click OK. -folder_props=Folder Properties - -# New User Wizard messages -new_user_title=New User Wizard -new_user_title_edit=Edit User Wizard -new_user_desc=This wizard helps you to add a user to the repository. -new_user_desc_edit=This wizard helps you modify a user in the repository. -new_user_step1_title=Step One - Person Properties -new_user_step1_desc=Enter information about this person. -new_user_step2_title=Step Two - User Properties -new_user_step2_desc=Enter information about this user. -new_user_finish_instruction=To add the user to this space click Finish. To review or change your selections click Back. -person_properties=Person Properties -user_properties=User Properties -first_name=First Name -last_name=Last Name -email=Email -company_id=Company ID -home_space_location=Home Space Location -home_space_name=Home Space Name - -# Trashcan messages -title_deleted_items=Deleted Items -title_delete_all=Delete All Items -title_recover_all=Recover All Items -title_recover_listed=Recover Listed Items -title_delete_listed=Delete Listed Items -title_delete_item=Delete Item -title_recover_item=Recover Item -title_recovery_report=Recovery Report -deleted_items=Deleted Items -manage_deleted_items=Manage Deleted Items -manage_deleted_items_description=Remove or recover previously deleted items -recover_all_items=Recover All Items -delete_all_items=Delete All Items -deleted_items_info=Use the Search to find deleted items by name or content and use the filters to reduce the list. -original_location=Original Location -deleted_date=Date Deleted -deleted_user=Deleted by User -recover=Recover -clear_search_results=Clear Search Results -search_deleted_items_name=Search by Name -search_deleted_items_text=Search by Content -deleted_items_for=for ''{0}'' -delete_item=Delete Item -delete_item_info=Permanently delete an item from the deleted file store -delete_item_confirm=Are you sure you want to permanently delete \"{0}\" from the deleted file store? The item cannot be recovered once this action has been performed. -recover_item=Recover Item -recover_item_info=Recover an item from the deleted file store -recover_item_confirm=Are you sure you want to recover \"{0}\" from the deleted file store? -delete_all_items_info=Permanently delete all files and spaces from the deleted file store -delete_all_items_confirm=Are you sure you want to permanently delete all files and spaces from the deleted file store? The items cannot be recovered once this action has been performed. -recover_all_items_info=Recover all files and spaces from the deleted file store -recover_all_items_confirm=Are you sure you want to recover all the deleted files and spaces from the deleted file store? -delete_listed_items=Delete Listed Items -delete_listed_items_info=Permanently delete the listed files and spaces from the deleted file store -delete_listed_items_confirm=Are you sure you want to permanently delete the following deleted files and spaces from the deleted file store? The items cannot be recovered once this action has been performed. -recover_listed_items=Recover Listed Items -recover_listed_items_info=Recover the listed files and spaces from the deleted file store -recover_listed_items_confirm=Are you sure you want to recover the following deleted files and spaces from the deleted file store? -recovered_item_success=The item \"{0}\" has been successfully recovered. -recovered_item_parent=Failed to recover the item \"{0}\" as the parent folder is missing, please select a new folder destination. -recovered_item_parent_short=Parent folder missing -recovered_item_permission=Failed to recover the item \"{0}\" as you do not have the appropriate permissions to restore the item to the parent folder, please select a new folder destination. -recovered_item_permission_short=No permissions to write -recovered_item_integrity=Failed to recover the item \"{0}\" as there is now an item in the parent folder with the same name, please select a new folder destination. -recovered_item_integrity_short=Item with same name exists -recovered_item_failure=Failed to recover the item \"{0}\" due to error: {1} -recovered_item_failure_short=Failed -delete_item_success=The item \"{0}\" has been permanently deleted. -title_deleted_item_details=Deleted Item Details -deleteditem_details_description=Details of the deleted item -alternative_destination=You may select a destination where you wish the recovered items to be placed. If you do not select a destination, the original location of the item is used. Recovery of an item may fail if the destination does not exist or you do not have permission to add items there. -user_filter_who=Who -user_filter_all=All -user_filter_user=User -date_filter_when=When -date_filter_all=All -date_filter_today=Today -date_filter_week=Last 7 days -date_filter_month=Last 30 days -recovery_report=Recovered Items Report -recovery_report_info=The results of recovering the selected items -recovery_report_success=The following items were recovered successfully -recovery_report_failed=The following items failed to recover -recovery_report_reason=Failure Reason - -# My Alfresco messages -my_alfresco=My Alfresco -title_my_alfresco=My Alfresco -dashboard_info=My Alfresco Dashboard -dashboard_description=Configure this view and build your personal Alfresco dashboard -configure=Configure -error_dashboard=An error occurred in one of the dashlets. - -# My Alfresco Layout Manager wizard messages -configure_dashboard_title=Configure Dashboard Wizard -configure_dashboard_desc=This wizard helps you configure your dashboard layout and contents -step_layout=Layout -configure_dashboard_step1_title=Step One - Select Layout -configure_dashboard_step1_desc=Choose the layout and number of columns for your dashboard. -step_columns=Components -configure_dashboard_step2_title=Step Two - Select Components -configure_dashboard_step2_desc=Select the components for your dashboard and add them to the columns. -configure_dashboard_finish_instruction=To save the dashboard configuration click Finish. To review or change your selections click Back. -select_layout=Select the style of layout for your dashboard. Changing your existing dashboard layout to another with less columns will result in the additional columns being removed. -select_column=Select the column to configure -dashlet_list=Available Components -dashlet_btn_select=Add -dashlet_btn_remove=Remove -selected_dashlets=Selected Components -dashboard_column=Column -column_max_components=this column can display {0} components - -# My Alfresco Layouts messages -layout_single_label=Single Column -layout_single_desc=This layout displays components in a single column the full width of the page -layout_narrow_left_label=Two Column Narrow Left -layout_narrow_left_desc=This layout display components in two columns with a narrow left hand column -layout_narrow_right_label=Two Column Narrow Right -layout_narrow_right_desc=This layout display components in two columns with a narrow right hand column -layout_three_column_label=Three Column -layout_three_column_desc=This layout displays components across three columns of equal width - -# My Alfresco Dashlet components messages -dashlet_gettingstarted_label=Getting Started -dashlet_gettingstarted_desc=Displays helpful information for getting started with the Alfresco web-client -dashlet_mydocuments=My Documents -dashlet_mydocuments_desc=Displays a list of the documents in my home space -dashlet_checkedoutdocs=My Checked Out Documents -dashlet_checkedoutdocs_desc=Displays a list of the documents checked out to me -dashlet_myimages=My Images -dashlet_myimages_desc=Displays a 3 column thumbnail list of the images in my home space -gettingstarted_commontasks=Common Tasks -gettingstarted_browse=Browse items in your home space -gettingstarted_browse_desc=From the toolbar click My Home to display a list of items in the space. -gettingstarted_createspace=Create a space in your home space -gettingstarted_createspace_desc=Navigate to your home space. In the space info area, from the menu under Create, click Create Space and enter information about the space. -gettingstarted_addcontent=Add content to your home space -gettingstarted_addcontent_desc=Navigate to your home space. In the space info area, click Add Content and enter information about the content to be uploaded. -gettingstarted_createcontent=Create content in your home space -gettingstarted_createcontent_desc=Navigate to your home space. In the space info area, click Create Content under Create and enter information about the content to be created. -gettingstarted_demonstration=Demonstration -gettingstarted_demonstration_desc=Watch a demonstration of how Alfresco can be used to create a marketing launch. -gettingstarted_featuretour=Feature Tour -gettingstarted_featuretour_desc=Take a tour of some of the key features in Alfresco. -gettingstarted_onlinehelp=Online Help -gettingstarted_onlinehelp_desc=Click the help icon to display Alfresco Help topics. - -# User Console and Settings messages -title_user_console=User Options -title_edit_user_details=Edit User Details -user_console=User Options -user_console_info=User Options -user_console_description=Use this page to change your options and settings -my_details=My Details -general_pref=General Preferences -change_my_password_description=Use this view to change your password -change_my_password_instructions=Enter your new password. -old_password=Old Password -new_password=New Password -edit_user_details=Edit User Details -edit_user_details_description=Use this view to change your user details and email address -start_location=Start Location - -# Delete Space Dialog messages -select_delete_operation=What do you want to delete? -delete_op_all=This space and all its contents. Note: Rules will also be deleted. -delete_op_files=Only the files within this space. -delete_op_folders=Only the folders within this space. -delete_op_contents=Files and folders within this space. - -# Email users dialog -email_space_users=Email Space users -email_space_users_desc=Send an email to the users and groups assigned to this space. - -# Workflow messages -modify_workflow_props=Modify Properties of Simple Workflow -name_approve_step=Name for approve step -name_reject_step=Name for reject step -select_reject_step=Do you want to provide a reject step? -choose_copy_move_location=Choose whether you want to move or copy the content and also the location. -start_workflow=Start Workflow -start_workflow_wizard=Start New Workflow Wizard -start_workflow_desc=This wizard helps you start a workflow for an item in the repository. -available_workflows=Available workflows -step_choose_workflow=Choose Workflow -start_workflow_choose_title=Choose Workflow -start_workflow_choose_desc=Choose the workflow you want to start -step_workflow_options=Workflow Options -start_workflow_options_title=Workflow Options -start_workflow_options_desc=Select options for the workflow -start_workflow_finish_instruction=To start the workflow press Finish. To review or change your selections click Back. -start_workflow_no_metadata=There is no metadata to collect for this particular workflow. -start_workflow_no_workflows=There are no workflows currently deployed in the repository, press Cancel to exit the wizard. -users_and_roles=Users and their Roles -resources=Resources -manage_task=Manage Task -manage_task_title=Manage Task -manage_task_desc=This dialog allows the task to be managed. -view_completed_task_title=View Completed Task -view_completed_task_desc=This dialog allows the completed task details to be viewed. -task_properties=Task Properties -id=Id -status=Status -completed=Completed -source=Source -priority=Priority -my_tasks_todo_title=My Tasks To Do -my_tasks_todo_desc=List of your tasks still to complete -my_tasks_completed_title=My Completed Tasks -my_tasks_completed_desc=List of your completed tasks -due_date=Due Date -completed_on=Completed on -outcome=Outcome -reassign=Reassign -cancel_workflow=Cancel Workflow -cancel_workflow_info=To cancel this workflow, click OK. -cancel_workflow_confirm=Are you sure you want to cancel the \"{0}\" workflow? -error_cancel_workflow=Unable to cancel the workflow due to system error: -reassign_task_title=Reassign Task -reassign_task_desc=This dialog allows you to reassign a task. -reassign_select_user=Select the user to assign the task to, then press OK. -error_reassign_task=Unable to reassign the task due to system error: -part_of_workflow=Part of Workflow -initiated_by=Initiated by -start_date=Start date -add_resource=Add Resource -view_properties=View Content Properties -edit_properties=Edit Content Properties - -# Admin Console messages -title_admin_console=Administration Console -admin_console=Administration Console -admin_description=Use this view to perform system administration functions. -admin_limited_license=Licensed: {0} license granted to {1} and limited to {3} days expiring {4,date,short} ({5} days remaining - issued on {2,date,short}). -admin_unlimited_license=Licensed: {0} license granted to {1} and does not expire (issued on {2,date,short}). -admin_invalid_license=Licensed: LICENSE INVALID - Alfresco Repository restricted to read-only capability. - -# UI Page Titles -title_about=About Alfresco -title_login=Alfresco Web Client - Login -title_relogin=Alfresco Web Client - Logged Out -title_error=Alfresco Web Client - System Error -title_browse=Alfresco Web Client -title_inviteusers_invite=Invite Users - Invite -title_inviteusers_notify=Invite Users - Notify -title_invitecontentusers_invite=Invite Content Users - Invite -title_invitecontentusers_notify=Invite Content Users - Notify -title_change_user_roles=Change User Roles -title_remove_invited_user=Remove Invited User -title_advanced_search=Advanced Search -title_checkin_file=Check In File -title_checkout_file=Check Out File -title_checkout_file_link=Check Out File Download -title_delete_file=Delete File -title_delete_rule=Delete Rule -title_delete_user=Delete User -title_delete_space=Delete Space -title_file_details=Document Details -title_file_preview=Preview In Template -title_edit_categories=Edit Categories -title_edit_doc_props=Edit Document Properties -title_edit_link_props=Edit Link Properties -title_edit_file=Edit File -title_edit_html_inline=Edit HTML File Inline -title_edit_text_inline=Edit Text File Inline -title_edit_simple_workflow=Edit Simple Workflow -title_edit_space=Edit Space Details -title_rules=Space Rules -title_space_details=Space Details -title_apply_template=Apply Template -title_system_info=System Information -title_undo_checkout=Undo Check Out -title_update_file=Update File Content -title_users=User Management -title_invited_users=Manage Invited Users -title_content_users=Manage Content Users -title_add_content=Add Content -title_create_content=Create New Content -title_create_content_props=Create New Content - Properties -title_create_content_summary=Create New Content - Summary -title_action_add_feature=Add Feature Action -title_action_remove_feature=Remove Feature Action -title_action_checkin=Check In Action -title_action_checkout=Check Out Action -title_action_copy=Copy Action -title_action_move=Move Action -title_action_script=Script Action -title_action_import=Import Action -title_action_email=Email Action -title_action_link_category=Link Category Action -title_action_simple_workflow=Simple Workflow Action -title_action_transform=Transform Action -title_action_transform_image=Transform Image Action -title_action_specialise_type=Specialise Type Action -title_condition_contains_text=Contains Text Condition -title_condition_has_mimetype=Has Mimetype Condition -title_condition_in_category=In Category Condition -title_condition_is_subtype=Is Subtype Condition -title_condition_has_aspect=Has Aspect Condition -title_new_user_person_props=User - Person Properties -title_new_user_user_props=User - User Properties -title_new_user_summary=User - Summary -title_export=Export -title_import=Import -title_admin_store_browser=Alfresco Store Browser -title_admin_node_browser=Alfresco Node Browser -title_admin_search_results=Node Browser Search Results -title_forums=Forum Space -title_forum=Forum -title_topic=Topic -title_delete_forums_space=Delete Forum Space -title_delete_forum_space=Delete Forum -title_delete_topic_space=Delete Topic -title_delete_post=Delete Post -title_create_forums=Create Forum Space -title_create_forum=Create Forum -title_forums_details=Forum Space Details -title_forum_details=Forum Details -title_create_topic=Create Topic -title_create_discussion=Create Discussion -title_topic_details=Topic Details -title_create_post=Post Message -title_create_reply=Create Reply -title_edit_forums=Edit Forum Space Details -title_edit_forum=Edit Forum Details -title_edit_topic=Edit Topic Details -title_edit_post=Edit Post -title_noaccess=No Access - -# UI Error messages -error_generic=A system error happened during the operation: {0} -error_noderef=Unable to find the repository item referenced by Id: {0} - the record has probably been deleted from the database. -error_deleted_folder=The folder item referenced by Id: {0} - has been deleted from the database. The system has changed your folder location as the folder you were in no longer exists. -error_homespace=The Home Space node referenced by Id: {0} cannot be found. It may have been deleted from the database. Please contact your system administrator. -error_search=Search failed due to system error: {0} -error_exists=A Space or File with that name already exists: {0} -error_delete_space=Unable to delete Space due to system error: -error_delete_file=Unable to delete File due to system error: -error_checkout=Unable to check out Content Node due to system error: -error_update=Unable to update Content Node due to system error: -error_cancel_checkout=Unable to cancel check out of Content Node due to system error: -error_checkin=Unable to check in Content Node due to system error: -error_paste=Unable to paste item due to system error: -error_login_user=Unable to login - unknown username/password. -error_login_missing=Must specify username and password. -error_delete_rule=Unable to delete Rule due to system error: -error_actions=Failed to run Actions due to error: {0} -error_rule=Failed to create Rule due to error: {0} -error_space=Failed to create new space due to error: {0} -error_person=Failed to create Person due to error: {0} -error_delete_user=Failed to delete User due to error: {0} -error_delete_user_object=Unable to delete the User object associated with the Person. This is not an error if an external authentication mechanism such as NTLM was previously active. -error_remove_user=Failed to remove User due to error: {0} -error_password_match=Please ensure that both password fields contain the same value. -error_property=Property ''{0}'' is not available for this node -error_create_space_dialog=Please correct the errors below then click Create Space. -error_create_forums_dialog=Please correct the errors below then click Create Forum Space. -error_create_forum_dialog=Please correct the errors below then click Create Forum. -error_create_topic_dialog=Please correct the errors below then click Create Topic. -error_create_post_dialog=Please correct the errors below then click Post. -error_create_reply_dialog=Please correct the errors below then click Reply. -error_create_category_dialog=Please correct the errors below then click New Category. -error_create_group_dialog=Please correct the errors below then click Create Group. -error_dialog=Please correct the errors below then click OK. -error_wizard=Please correct the errors below then click Finish. -error_update_category=Failed to update category due to system error: {0} -error_update_simpleworkflow=Failed to update simple workflow due to system error: {0} -error_workflow_approve=Failed to approve the document due to system error: {0} -error_workflow_reject=Failed to reject the document due to system error: {0} -error_aspect_classify=Failed to apply the ''classifiable'' aspect to the document due to system error: {0} -error_aspect_classify_space=Failed to apply the ''classifiable'' aspect to the space due to system error: {0} -error_aspect_versioning=Failed to apply the ''versionable'' aspect to the document due to system error: {0} -error_aspect_inlineeditable=Failed to apply the ''inlineeditable'' aspect to the document due to system error: {0} -error_content_missing=The node''s content is missing: \n node: {0} \n reader: {1} \nPlease contact your system administrator. -error_export=Failed to execute export: {0} -error_import=Failed to execute import: {0} -error_import_no_file=Can not find an ACP file to import! -error_import_empty_file=You can not import an empty ACP file! -error_import_all=Please correct the import errors below then click OK. -error_export_all=Please correct the export errors below then click OK. -error_save_search=Failed to save search due to error: {0} -error_restore_search=Failed to restore saved search due to error: {0} -error_shortcut_permissions=Unable to navigate to the item as it cannot be read by this user. Another user may have modified the permission. - -# Confirmations -return_to_application=Return to application -delete_space_info=To remove this space and all of its contents, click OK. -delete_space_confirm=Are you sure you want to delete \"{0}\" and all its contents? -delete_forums_info=To remove this forum space and its contents, click OK. -delete_forum_info=To remove this forum and its topics, click OK. -delete_forum_confirm=Are you sure you want to delete \"{0}\" and all its topics? -delete_topic_info=To remove this topic and its posts, click OK. -delete_topic_confirm=Are you sure you want to delete \"{0}\" and all its posts? -delete_post_info=To remove this post from the topic, click OK. -delete_post_confirm=Are you sure you want to delete the post from \"{0}\"? -delete_file_info=To remove this file and any previous versions, click OK. -delete_file_confirm=Are you sure you want to delete \"{0}\" and all previous versions? -delete_rule_info=To remove this rule from the space, click Yes. -delete_user_info=To delete this user from the system, click Yes. -delete_rule_confirm=Are you sure you want to delete \"{0}\"? -delete_user_confirm=The User will no longer be able to access the system. Are you sure you want to delete user \"{0}\"? -remove_invited_user_confirm=The User will no longer be able to access the documents and folders in this space. Are you sure you want to remove user \"{0}\"? -remove_content_user_confirm=The User will no longer be able to access this content. Are you sure you want to remove user \"{0}\"? -delete_companyroot_confirm=WARNING: This folder is a special folder accessed by all Users! Please be sure that you wish to delete this folder. It may cause System Errors if you remove it. - -# Status Messages -status_space_created=Successfully created space ''{0}''. -status_space_deleted=Successfully deleted space ''{0}''. -status_space_updated=Successfully updated space ''{0}''. - -# Validation Messages -validation_mandatory={0} is a mandatory field. -validation_string_length={0} must be between {1} and {2} characters in length. -validation_regex={0} is invalid. -validation_regex_not_match={0} is invalid. -validation_numeric_range={0} must be between {1} and {2}. -validation_invalid_character=is an invalid character. +# I18N message properties + +# Date Pattern +date_pattern=d MMMM yyyy +date_time_pattern=d MMMM yyyy HH:mm +time_pattern=HH:mm + +# General UI +product_name=Alfresco +view_description=This view allows you to browse the items in this space. +search_description=This view allows you to see the results from your search. +checkinfile_description=Check in your working copy for other team members to work with. +checkoutfilelink_description=Edit the checked out file, undo the check out or carry on working. +checkoutfile_description=Enter information about the check out. +documentdetails_description=View the details about the content. +linkdetails_description=View the details about the link object. +previewdocument_description=Preview the content or space within a Template. +spacedetails_description=View the details about the space. +undocheckoutfile_description=Cancel the check out of a document and discard any changes. +updatefile_description=Update a document on the repository with content from your computer. +editfile_description=Edit the content of the file. +editfileinline_description=Edit the content of the document, then click Save. +createfile_description=Enter new content. +manageusers_description=Manage the users of the repository. +manage_invited_users_description=Manage the permissions you have granted to users who access your space. +manage_content_users_description=Manage the permissions you have granted to users who access your content. +modify_user_roles_description=Modify the permissions granted to a user for accessing your space. +modify_content_user_roles_description=Modify the permissions granted to a user for accessing your content. +advancedsearch_description=Perform a more detailed search of the repository. +edit_content_description=Modify the content properties then click OK. +view_content_description=View the content properties. +editcategory_description=Set the category for the document then click OK. +editcategory_space_description=Set the category for the space then click OK. +editworkflow_description=Modify the simple workflow properties then click OK. +editspace_description=Modify the space properties then click OK. +editlink_description=Modify the link object properties then click OK. +create_space_description=Enter information about the new space then click Create Space. +space_rules_description=This view shows you all the rules to be applied to content in this space. +warning_inline=This is only recommended for HTML or plain text documents. +categories_description=This view allows you to browse and modify the categories hiearchy. +new_category_description=Enter information about the new Category then click Create Category. +status_message_default=No messages. +no_icons_found=No icons found + +# UI Component messages +yes=Yes +no=No +kilobyte=KB +megabyte=MB +gigabyte=GB +locked_you=Item locked by you +locked_user=Item locked by user +wizard_errors=Please correct the errors below. +last_page=Last Page +next_page=Next Page +prev_page=Previous Page +first_page=First Page +page_info=Page {0} of {1} +go_up=Go Up +ok=OK +go=Go +to=To +from=From +options=Options +other_options=Other Options +local=Local +inherited=Inherited +search=Search +advanced_search=Advanced Search +value_not_set=not set +clear=Clear Results +results_contains=Results for ''{0}''. +results_contains_filter=Results for ''{0}'' in ''{1}''. +details_view=Details View +view_icon=Icon View +view_browse=Browse View +create_options=Create +more_options=More... +more_actions=More Actions +more_options_space=More Actions for this Space +more_options_file=More Actions for this Document +select_space_prompt=Click here to select a Space +select_existing_space_prompt=Click here to select an existing Space +select_home_space_prompt=Click here to select the Home Space location +select_category_prompt=Click here to select a Category +select_destination_prompt=Click here to select the destination +add_new=Add New +change=Change +set=Set +no_categories_applied=This document does not yet have any categories applied. +has_following_categories=This document has the following categories applied... +no_categories_applied_space=This space does not yet have any categories applied. +has_following_categories_space=This space has the following categories applied... +moved=moved +copied=copied +document_action=The document will be {0} to ''{1}'' if the ''{2}'' action is taken. +clipboard=Clipboard +recent_spaces=Recent Spaces +shortcuts=Shortcuts +company_home=Company Home +my_home=My Home +guest_home=Guest Home +new_search=New Search +search_results=Search Results +search_detail=Search for \"{0}\" results shown below +close_search=Close Search +browse_spaces=Browse Spaces +browse_content=Content Items +location=Location +toggle_shelf=Hide or Show the Shelf +shelf=Shelf +actions=Actions +view=View +view_details=View Details +view_details_file=View Details for file +change_details=Change Details +update=Update +cut=Cut +copy=Copy +paste=Paste +remove=Remove +change_roles=Change Roles +change_user_roles=Change User Roles +paste_item=Paste Item +paste_link=Paste As Link +paste_all=Paste All +remove_item=Remove Item +remove_all=Remove All +close=Close +invite=Invite +invite_user=Invite User +filter_contents=Filter Contents +users=Users +groups=Groups +resetall=Reset All +content_rules=Content Rules +view_in_browser=View In Browser +view_in_webdav=View in WebDAV +view_in_cifs=View in CIFS +download_content=Download Content +details_page_bookmark=External Access URL +noderef_link=Alfresco Node Reference +links=Links +create_shortcut=Create Shortcut +navigation=Navigation +next_item=Next Item +previous_item=Previous Item +cancel=Cancel +upload=Upload +homespace=Home Space +network_folder=Open Network Folder +other_action=Run Action +information=Information +move=Move +type=Type +aspect=Aspect +workflow=Workflow +workflows=Workflows +rules=Rules +system_error=System Error +login=Login +templates=Templates +template=Template +rss_template=RSS Template +select_button=Select... +select_items=Select items +select_an_item=Select an item +selected_items=Selected Items +add_to_list_button=Add to List +none=None +no_selected_items=No selected items. +search_select_item=Search for and select an item. +search_select_items=Search for and select items. +search_minimum=Not enough information was entered to perform a search, at least one value must be entered or a location selected to search within. Text fields require a minimum of {0} characters. +filter=Filter +choose_icon=Choose icon +security=Security +all_formats=All Formats +rules_count=Number of rules applied to this Space +working_copy_document=Working Copy +copy_of=Copy of +link_to=Link to +icon=Icon +lock=Lock +unlock=Unlock +items_per_page=Items Per Page +raise_issue=Raise an Issue +click_to_set_date=Click to set a date +today=Today +reset=Reset + +# Properties +username=User Name +joindate=Join Date +roles=Roles +help=Help +name=Name +password=Password +confirm=Confirm +path=Path +description=Description +created=Created +modified=Modified +created_date=Created Date +modified_date=Modified Date +size=Size +title=Title +author=Author +date=Date +mimetype=Format + +# Repo permission display labels +# Note - these come from the server, the english translation is generally the same +Administrator=Administrator +Consumer=Consumer +Read=Consumer +Coordinator=Coordinator +Collaborator=Collaborator +Contributor=Contributor +Editor=Editor +All=All +ContentManager=Content Manager +ContentPublisher=Content Publisher +ContentContributor=Content Contributor +ContentReviewer=Content Reviewer + +# Actions +delete=Delete +edit=Edit +checkin=Check In +checkout=Check Out +checkout_document=Check out this document +undocheckout=Undo Check Out +delete_space=Delete Space +delete_file=Delete File +delete_rule=Delete Rule +delete_user=Delete User +remove_user=Remove User +create_space=Create Space +add_content=Add Content +create_content=Create Content +create_form=Create Form +add_multiple_files=Add Multiple Files +import_directory=Import Directory +advanced_space_wizard=Advanced Space Wizard +create_rule=Create Rule +reapply_rules=Reapply Rules +reapply_rules_success=The rules have been successfully reapplied. +ignore_inherited_rules=Ignore Inherited Rules +include_inherited_rules=Include Inherited Rules +inherited_rules_being_ignored=Inherited rules are being ignored. +manage_rules=Manage Content Rules +manage_users=Manage System Users +manage_groups=Manage User Groups +manage_invited_users=Manage Space Users +manage_content_users=Manage Content Users +modify_user_roles=Modify User Roles for +modify=Modify +logout=Logout +add=Add +node_browser=Node Browser +reset_config=Reset Config Service +save=Save +user_details=User Details +language=Language +export=Export +import=Import +take_ownership=Take Ownership +create_forums=Create Forum Space +create_forum=Create Forum +create_topic=Create Topic +create_post=Create Post +create_reply=Post Reply +delete_forums=Delete Forum Space +delete_forum=Delete Forum +delete_topic=Delete Topic +delete_post=Delete Post +post_to_topic=Post to Topic +reply=Reply +edit_post=Edit Post +reply_to=Reply to +post_reply=Post Reply + +# Login page message +login_details=Enter Login details +login_err_password_length=Password must be between {0} and {1} characters in length. +login_err_password_chars=Password can only contain characters or digits. +login_err_username_length=Username must be between {0} and {1} characters in length. +login_err_username_chars=Username can only contain characters or digits. +loggedout_details=You have been logged out of Alfresco. +relogin=Re-login to Alfresco +no_access=You have no access to Alfresco. +no_cookies=Cookies must be enabled in your browser for the Alfresco Web-Client to function correctly. +user_err_user_name=User ID cannot contain the characters: {0} + +# Browse list messages +no_space_items=No items to display. Click the ''{0}'' action to create a space. +no_content_items=No items to display. To add an existing document click ''{0}'' action. To create an HTML or Plain Text file click ''{1}'' action. + +# Advanced Search messages +look_in=Look in location +look_for=Look for +all_spaces=All Spaces +specify_space=Specify Space +include_child_spaces=Include child spaces +show_results_for=Show me results for +all_items=All Items +file_names_contents=File names and contents +file_names=File names only +space_names=Space names only +show_results_categories=Show me results in the categories +include_sub_categories=Include sub-categories +also_search_results=More search options +additional_options=Additional options +save_new_search=Save New Search +save_edit_search=Save Modified Search +saved_searches=My Saved Search Options +title_save_search=Save Search Query +save_search_description=Save a search query for use again later +search_props=Saved Search Properties +select_saved_search=Select a Saved Search... +saved_search_warning=This operation will attempt to overwrite the existing saved search ''{0}'' +user_searches=Your Searches +global_searches=Public Searches +save_search_global=Save as a public search available to all users. +folder_type=Folder Type + +# Forum messages +forums=Forum Space +forum=Forum +browse_forums=Browse Forum Spaces and Forums +browse_topics=Browse Topics +browse_posts=Browse Posts +forums_info=This view allows you to browse forum spaces and forums. +forum_info=This view allows you to browse topics in this forum. +topic_info=This view allows you to browse posts in this topic. +no_forums=No forum spaces or forums to display. Click the ''Create Forum Space'' action to create a forum space or ''Create Forum'' action to create a forum. +no_topics=No topics to display. Click the ''Create Topic'' action to create a topic. +no_posts=No posts to display. Click the ''Post to Topic'' action to create a post. +topic=Topic +topics=Topics +post=Post +posted=Posted +create_forums_description=Enter information about the new forum space then click Create Forum Space. +create_forum_description=Enter information about the new forum then click Create Forum. +create_topic_description=Enter information about the new topic then click Create Topic. +create_post_description=Enter the content of the message then click Post. +create_reply_description=Enter message text to reply then click Reply. +modify_forums_properties=Modify Forum Space Properties +forums_props=Forum Space Properties +modify_forum_properties=Modify Forum Properties +forum_props=Forum Properties +topic_props=Topic Properties +modify_topic_properties=Modify Topic Properties +create_forums_finish=To create the forum space click Create Forum Space. +create_forum_finish=To create the forum click Create Forum. +create_topic_finish=To create the topic click Create Topic. +create_post_finish=To create the post click Post. +create_reply_finish=To create the reply click Reply. +forums_details_description=View details about the forum space. +forum_details_description=View details about the forum. +topic_details_description=View details about the topic. +bubble_view=Bubble View +replies=Replies +on=On +reply_message=Reply Message +edit_forums_description=Modify the forum space properties then click OK. +edit_forum_description=Modify the forum properties then click OK. +edit_topic_description=Modify the topic properties then click OK. +edit_post_description=Modify the message then click OK. +edit_post_finish=To save the message click Post. +start_discussion=Start Discussion +discuss=View Discussions +discussion_for={0} discussion + +# Common Wizard messages +steps=Steps +summary=Summary +summary_desc=The information you entered is shown below. +default_instruction=To continue click Next. +next_button=Next +back_button=Back +finish_button=Finish +cancel_button=Cancel +clear_button=Clear +you_may_want=You may want to +summary_step_description=Shows a summary of the information entered. + +# Category Management messages +title_categories_list=Categories +add_category=Add Category +edit_category=Edit Category +delete_category=Delete Category +category_icons=Categories +category_details=Details +category_management=Category Management +title_create_category=Create New Category +new_category=New Category +category_props=Category Properties +items=Items +title_delete_category=Delete Category +delete_category_warning=This category has {0} existing document(s) linked to it. +delete_category_info=To remove this category and all it's sub-categories, click Delete. +delete_category_confirm=Are you sure you want to delete category \"{0}\" and all sub-categories? +title_edit_category=Edit Category +edit_category_description=Edit the information for this category. + +# Groups Management messages +title_groups_list=Groups Management +groups_management=Groups Management +groups_description=Manage the members of a group, create new groups or remove existing groups. +new_group=Create Group +new_sub_group=Create Sub-Group +edit_group=Edit Group +delete_group=Delete Group +add_user=Add User +group_icons=Groups +group_details=Details +root_groups=Root Groups +group_filter_children=Children +group_filter_all=All +title_create_group=Create Group +new_group_description=Enter information about the new Group then click Create Group. +group_props=Group Properties +identifier=Identifier +create_group_warning=The Identifier for a Group cannot be changed once it has been set. +title_delete_group=Delete Group +delete_group_warning=This group has {0} sub-group(s) or user(s) attached to it. +delete_group_info=To delete this Group from the system and remove all members from it, click Delete. +delete_group_confirm=Once the group is removed from the system it will no longer be accessable. Are you sure you want to delete Group \"{0}\" and remove all users from it? +title_add_user_group=Add User to Group +add_user_group_description=Add an existing User to a Group +select_users=Select Users to add to this Group +selected_users=Selected Users +groups_err_group_name=Group ID cannot contain the characters: {0} +groups_err_exists=A group ID with the same name already exists, group identifiers must be unique. + +# Invite Users Wizard messages +invite_title=Invite Users Wizard +invite_desc=This wizard helps you to give other users access to your space. +invite_step1_title=Step One - Invite Users +invite_step1_desc=Select the users and roles they will play in this space. +invite_step2_title=Step Two - Notify Users +invite_step2_desc=Notify the selected users. +i_want_to=I want to... +invite_step_1=Invite Users +invite_step_2=Notify Users +invite_all=Invite All users as guests +invite_users=Specify Users/Groups and their roles +specify_usersgroups=Specify Users/Groups +select_usersgroups=Select user/group and their role(s) +select_role=Select role +selected_usersgroups=Selected users/groups and their role(s) +selected_roles=Selected roles +click_add=Click Add +role=Role +send_email=Do you want to send an email to notify the invited users? +subject=Subject +body=Body +automatic_text=Automatic text +invited_to=You have been invited to ''{0}'' by {1}. +invite_role=You will have the role of: {0} +invite_finish_instruction=To close this wizard and apply your changes click Finish. To review or change your selections click Back. +remove_invited_user_info=To remove an invited user from this space, click Yes. +add_role=Add Role +space_owner=User ''{0}'' is the current owner of this space. +users_and_groups=Users and Groups +authority=Username + +# Invite Content Users Wizard messages +invite_content_title=Invite Content Users Wizard +invite_content_desc=This wizard helps you to give other users access to your content. +invite_content_step1_desc=Select the users and roles they will play for this content. +remove_content_user_info=To remove an invited user from this content, click Yes. +content_owner=User ''{0}'' is the current owner of this content. + +# System Users messages +create_user=Create User +change_password=Change Password +title_change_password=Change User Password +change_password_description=Use this view to change an existing user password. +change_password_instructions=Enter the new password for this user. +show_all=Show All + +# Check-in messages +check_in=Check In +checkin_options=Check In options +checkin_changes_info=Check in changes and keep file checked out +workingcopy_location=Working copy location +which_copy_checkin=Which copy do you want to check in? +which_copy_current=Use copy in current space +which_copy_other=Use copy uploaded from my computer +locate_doc_upload=Locate document to upload +file_location=Location +minor_change=Minor Change +major_change=Major Change +notes=Notes + +# Check-out messages +check_out=Check Out +copy_work_with=A copy of the file ''{0}'' will be made for you to work with. +copy_checkin_changes=When you have completed your changes you need to check in the file to allow others to view the changes. +copy_store_prompt=Where do you want to keep the copy of this file? +store_space_current=In the current space +store_space_selected=In the space selected +check_out_of=Check Out of +copy_file_checkedout=A copy of the file ''{0}'' is now checked out to you for editing. +edit_workingcopy_title=Edit the working copy now +edit_workingcopy_info=To edit the working copy of the file, click the link below and if asked select Save. +continue_working_title=Continue working +continue_working_info1=If you do not want to edit the file now click OK to close the page. +continue_working_info2=To edit the file in the future click the Edit action +download_complete=When the download is complete, click OK. +undo_checkout_for=Undo Check Out for +undo_checkout=Undo Check Out +undo_checkout_info=If you undo the check out of a document, the associated working copy will be deleted and all changes to it since the Check Out will be lost. + +# Document and Space details messages +details_of=Details of +preview_of=Preview of +modify_props_of=Modify Properties of +modify_space_properties=Modify Space Properties +modify_content_properties=Modify Content Properties +view_content_properties=View Content Properties +preview=Preview in Template +custom_view=Custom View +view_links=Links +not_inline_editable=This document is not inline editable. +allow_inline_editing=Allow Inline Editing +not_in_workflow=This document is not part of any workflow. +not_in_category=This document is not categorized. +not_in_category_space=This space is not categorized. +not_versioned=This document has no version history. +allow_categorization=Allow Categorization +allow_versioning=Allow Versioning +version_history=Version History +version=Version +document_properties=Document Properties +link_properties=Link Properties +general_properties=General Properties +other_properties=Other Properties +link_destination=Link Destination +link_destination_details=Link Destination Details +no_other_properties=This document does not have any other properties to show. +modify_categories_of=Modify categories of +space_props=Space Properties +choose_space_icon=Choose space icon +create_space_finish=To create your space click Create Space. +select_category=Select a category +selected_categories=Selected categories +no_selected_categories=No categories selected. +success_ownership=Successfully took ownership of the object. +success_unlock=Successfully unlocked the document. +inherit_permissions=Inherit Parent Space Permissions +success_inherit_permissions=Successfully changed Inherit Parent Permissions to 'Yes' +success_not_inherit_permissions=Successfully changed Inherit Parent Permissions to 'No' +apply_template=Apply Template +apply_dashboard_info=Select a template to be applied to the Space as a Custom view. +apply_dashboard_doc_info=Select a template to be applied to the Document as a Custom view. +apply_rss_feed=Apply RSS Feed Template +apply_rss_feed_info=Select a template to be applied to the Space as an RSS feed. +apply_rss_feed_warning1=This Space must be visible to the Guest user for the RSS feed to be publically viewable, you can Invite the Guest user using the +apply_rss_feed_warning2=view. +rss_feed=RSS Feed +rss_feed_link=RSS Feed Link + +# Export messages +export_info=Exports metadata and content from this or all Spaces. +export_from=Export From +package_name=Package Name +all_spaces_root=Complete Repository +current_space=Current Space +include_children=Include Children +include_self=Include this Space +run_export_in_background=Run export in background +export_error_info=If this option is selected the export will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. + +# Import messages +import_info=Imports an Alfresco content package file into the repository. +import_package_description=Alfresco content package +run_import_in_background=Run import in background +import_error_info=If this option is selected the import will be performed in the background so the results may not appear immediately. You may also want to monitor the server console and log files for errors. + +# Edit Content messages +edit_file_title=Edit file +edit_file_prompt=To edit the file ''{0}'', click the link below and if asked select Save. +edit_download_complete=When the download is complete, click Close. +checkout_file_title=Check out file +checkout_you_may_want=You may want to check out this file to lock it and prevent other users from editing it. +checkout_hint1=Hint: When you check out a file a copy is made for you to work with. +checkout_hint2=When you have finished editing the copy you need to check it in to release the lock and allow others to work with the modified document. +checkout_want_to=to prevent the possibility of other users overwriting your changes. +checkout_warn=Note: You will lose any changes already made to this document. +local_copy_location=Local copy location +locate_content_upload=Locate and upload your document to the repository. + +# System Information and admin page messages +system_info=System Information +current_user=Current User +http_app_state=HTTP Application State +http_session_state=HTTP Session State +http_request_state=HTTP Request State +http_request_params=HTTP Request Parameters +http_request_headers=HTTP Request Headers +repository_props=Repository Properties +repository_patch_succeeded=Succeeded +repository_patch_failed=FAILED +system_props=System Properties +hide_details=Hide Details +show_details=Show Details +user_search_info=To find a user search for them using their first name, last name and/or user name. Alternatively to see all users click 'Show All', however, this may take some time if there are a lot of users in the system. +user_change_homespace_info=Selecting a new home space for a user will not remove the existing permissions on the original home space. You may wish to use the Manage Space Users dialog to modify permissions if they are no longer required on the original home space. + +# Content Wizard messages +add_content_dialog_title=Add Content Dialog +add_content_dialog_desc=This dialog helps you to add content to a space. +upload_content=Upload Content +properties=Properties +general=General +file_name=File Name +content_type=Content Type +content_format=Content Format +inline_editable=Inline Editable +locate_content=Locate content to upload +content_location=Location +uploaded_content=Uploaded Content +click_upload=Click upload +file_upload_success=''{0}'' was uploaded successfully. +modify_props_when_page_closes=Modify all properties when this page closes. +modify_props_when_wizard_closes=Modify all properties when this wizard closes. +modify_props_help_text=Rules applied to this content may require you to enter additional information. +content_finish_instruction=To add the content to this space click Finish. To review or change your selections click Back. +create_content_title=Create Content Wizard +create_content_desc=This wizard helps you to create a new document in a space. +create_content_step1_title=Step One - Specify name and select type +create_content_step1_desc=Specify the name and select the type of content you wish to create. +create_content_step2_title=Step Two - Enter Content +create_content_step2_desc=Enter your document content into the repository. +enter_content=Enter Content +select_type=Select Type +content=Content +text_content=Plain Text Content +html_content=HTML Content +xml_content=XML Content +create_form_title=Create Form Wizard +create_form_desc=Create an XML Form definition from XML Schema and Templates +create_form_step1_title=Upload an XML Schema +create_form_step1_desc=Upload an XML Schema +create_form_step2_title=Edit the XML Schema +create_form_step2_desc=This is the generated XForm based on the schema provided. +selected_templateoutputmethods=Selected Template Output Methods +configure_output_methods_step1_desc=Upload template output method and specify the extension to use for its generated assets. +template_output_method_file=Template Output Method File +extension_for_generated_assets=Extension For Generated Assets +file_extension=Extension +schema=Schema +schema_root_tag_name=Root Tag +edit_xml_schema=Edit XML Schema +template_type=Template Type +configure_template_output_methods=Configure Template Output Methods + +# Rule and Action Wizard messages +run_action_title=Run Action Wizard +run_action_desc=This wizard helps you run an action +run_action_step1_title=Step One - Select Actions +run_action_finish_instruction=To execute the action click Finish. To review or change your selections click Back. +create_rule_title=Create Rule Wizard +create_rule_desc=This wizard helps you create a new rule. +create_rule_step1_title=Step One - Select Conditions +create_rule_step2_title=Step Two - Select Actions +create_rule_step3_title=Step Three - Enter Details +create_rule_finish_instruction=To create the rule click Finish. To review or change your selections click Back. +edit_rule_title=Edit Rule Wizard +edit_rule_desc=This wizard helps you modify a rule. +edit_rule_finish_instruction=To update the rule click Finish. To review or change your selections click Back. +select_action=Select Action +select_an_action=Select an action... +select_a_type=Select a type... +action=Action +action_settings=Action Settings +set_action_values=Set action values +select_feature=Select required feature +version_notes=Version Notes +checkout_location=Check out location +destination=Destination +message=Message +category=Category +categories=Categories +change_category=Change Category +approve_flow=Approve Flow +approve_step_name=Name for approve step +move_or_copy=Choose whether you want to move or copy the content and also the location. +reject_flow=Reject Flow +want_reject_step=Do you want to provide a reject step? +reject_step_name=Name for reject step +required_format=Required format +details=Details +select_condition=Select Condition +select_a_condition=Select a condition... +condition=Condition +conditions=Conditions +condition_settings=Condition Settings +set_condition_values=Set condition values +select_checkout_prompt=Click here to select the check out location +condition_contains_desc=Enter the text pattern required, including any wildcards. The file name includes the file type extension when matching. +file_name_pattern=File name pattern +condition_contains_hints=Hints +condition_contains_hints_desc=Use zz* to match any name that begins with zz; use *.txt to match any text file; use *zz* to match any file name that contains zz anywhere including the beginning or end. +apply_to_sub_spaces=Apply rule to sub spaces +run_in_background=Run rule in background +rule_disabled=Disable rule +not=Not +click_set_and_add=Click to set values and add to list +click_add_to_list=Click to add to list +set_and_add_button=Set Values and Add +selected_conditions=Selected Rule Conditions +selected_actions=Selected Rule Actions +condition_has_aspect=Item has aspect ''{0}'' +condition_has_aspect_not=Item does not have aspect ''{0}'' +condition_is_subtype=Item is a subtype of ''{0}'' +condition_is_subtype_not=Item is not a subtype of ''{0}'' +condition_compare_mime_type=Item has a mimetype of ''{0}'' +condition_compare_mime_type_not=Item does not have a mimetype of ''{0}'' +condition_in_category=Item is in category ''{0}'' +condition_in_category_not=Item is not in category ''{0}'' +condition_compare_property_value=Name property matches ''{0}'' +condition_compare_property_value_not=Name property does not match ''{0}'' +action_add_features=Add aspect ''{0}'' +action_remove_features=Remove aspect ''{0}'' +action_specialise_type=Item is specialised to type ''{0}'' +action_simple_workflow={0} item to ''{1}'' if ''{2}'' action is taken. +action_link_category=Link to category ''{0}'' +action_transform=Copies content to ''{0}'' and transforms to ''{1}'' +action_transform_image=Copies image to ''{0}'' and transforms to ''{1}'' using option ''{2}'' +action_copy=Copy to ''{0}'' +action_move=Move to ''{0}'' +action_mail=Send email to ''{0}'' +action_mail_template=Use Template +action_check_in=Check in content as ''{0}'' with comment ''{1}'' +action_check_out=Check out content to ''{0}'' +action_set_property_value=Sets property ''{0}'' +action_import=Imports to ''{0}'' +action_script=Executes script ''{0}'' +action_script_select=Select a script to execute +not_condition_result=Check the item does not match the criteria above +space=Space +import_to=Import To +encoding=Encoding +encoding_utf8=UTF-8 +rule_type=Rule Type +rule_background_info=If this option is selected the rule will execute in the background so the results may not appear immediately. +select_recipients=Search for email recipients by Name or Group +selected_recipients=Selected email recipients +message_recipients=Message Recipients +email_message=Email message +insert_template=Insert Template +discard_template=Discard Template + +# Create Space Wizard messages +create_space_title=Create Space Wizard +create_space_desc=This wizard helps you to create a new space. +create_space_step1_title=Step One - Starting Space +create_space_step1_desc=Choose how you want to create your space. +create_space_step2_title=Step Two - Space Options +create_space_step2_desc=Select space options. +create_space_step3_title=Step Three - Space Details +create_space_step3_desc=Enter information about the space. +create_space_finish_instruction=To close this wizard and create your space click Finish. To review or change your selections click Back. +scratch=Scratch +an_existing_space=An existing space +a_template=A template +creating_from=Creating From +save_as_template=Save As Template +template_name=Template Name +select_a_template=Select a template... +select_a_script=Select a script... +starting_space=Starting Space +space_options=Space Options +space_details=Space Details +how_to_create_space=How do you want to create your space? +from_scratch=From scratch +based_on_existing_space=Based on an existing space +using_a_template=Using a template +existing_space=Existing Space +copy_existing_space=Copy existing space +structure=Structure +structure_contents=Structure and contents +space_copy_note=Note: Any content rules for spaces will also be copied. +space_type=Space Type +space_type_create=Select the type of space you want to create. +container=Folder Space +container_desc=A place for keeping and organizing documents and other spaces. +forums_desc=A place to discuss content with other users. +space_type_note=Note: If you can only see one type of space then other space types may not be enabled. See your System Administrator for further help. +template_space=Template Space +select_template=Select the template you want to use. + +# Create Website Wizard messages +create_website=Create Website +create_website_title=Create Website Wizard +create_website_desc=This wizard helps you create a new website space. +website_details=Website Details +create_website_step1_title=Step One - Website Details +create_website_step1_desc=Enter the information about the website. +website_invite=Invite Users +create_website_step2_title=Step Two - Invite Users +create_website_step2_desc=Select users and their roles. +website_notify=Email Users +create_website_step3_title=Step Three - Notify Users +create_website_step3_desc=Notify the invited users. +create_website_finish_instruction=To close this wizard and create your website space click Finish. To review or change your selections click Back. +create_website_summary_users=Users and Roles + +# Delete Website Dialog messages +delete_website=Delete Website +delete_website_info=To remove this website and all associated user sandboxes, click OK. +delete_website_confirm=Are you sure you want to delete the website \"{0}\" and all associated user sandboxes? + +# Browse Website and Sandboxes messages +title_browse_website=Browse Website +website_info=Use this view to browse the staging area and user sandboxes for a website. +staging_sandbox=Staging Sandbox +user_sandboxes=User Sandboxes +sandbox_preview=Preview Website +sandbox_create=Create New Content +sandbox_browse=Browse Website +sandbox_submitall=Submit All +sandbox_icon=Browse Website +import_website_content=Import Website Content +title_browse_sandbox=Browse Sandbox +sandbox_info=Use this view to browse the files and folders within the sandbox for a website. +sandbox_title=Website ''{0}'' sandbox ''{1}'' +sandbox_staging=Staging +website_browse_folders=Browse Folders +website_browse_files=Browse Files +creator=Creator +modified_items=Modified Items +store_created_on=Created On +store_created_by=Created By +store_working_users=There are {0} user(s) working on this website. +avm_node_deleted=Deleted +submit=Submit +submit_success=Successfully submitted item: {0} +submitall_success=Successfully submitted sandbox for user: {0} +folder_preview=Preview Folder +file_preview=Preview File + +# Website actions and dialog messages +title_import_content=Import Content into Website +import_website_content_title=Import Content into Website +import_website_content_desc=Use this dialog to import an archive of content into the root of the website. +delete_avm_file_info=To remove this file from the sandbox, click OK. +delete_avm_file_confirm=Are you sure you want to remove \"{0}\" from the sandbox? +delete_folder=Delete Folder +delete_avm_folder_info=To remove this folder and its contents from the sandbox, click OK. +delete_avm_folder_confirm=Are you sure you want to remove \"{0}\" and its contents from the sandbox? +error_delete_folder=Unable to delete Folder due to system error: +create_web_content_title=Create Web Content Wizard +create_web_content_desc=This wizard helps you to create a new content item for a website. +create_folder=Create Folder +create_avm_folder_info=Create a new folder in the website. +add_avm_content_dialog_desc=This dialog helps you to add content to a folder. +update_avm_file_desc=Update a file in the website with content from your computer. +file_details_desc=View details about the file. +title_folder_details=Folder Details +folder_details_desc=View details about the folder. +edit_file_properties=Edit File Properties +edit_file_description=Edit the properties of the file then click OK. +edit_folder_properties=Edit Folder Properties +edit_folder_description=Edit the properties of the folder then click OK. +folder_props=Folder Properties + +# New User Wizard messages +new_user_title=New User Wizard +new_user_title_edit=Edit User Wizard +new_user_desc=This wizard helps you to add a user to the repository. +new_user_desc_edit=This wizard helps you modify a user in the repository. +new_user_step1_title=Step One - Person Properties +new_user_step1_desc=Enter information about this person. +new_user_step2_title=Step Two - User Properties +new_user_step2_desc=Enter information about this user. +new_user_finish_instruction=To add the user to this space click Finish. To review or change your selections click Back. +person_properties=Person Properties +user_properties=User Properties +first_name=First Name +last_name=Last Name +email=Email +company_id=Company ID +home_space_location=Home Space Location +home_space_name=Home Space Name + +# Trashcan messages +title_deleted_items=Deleted Items +title_delete_all=Delete All Items +title_recover_all=Recover All Items +title_recover_listed=Recover Listed Items +title_delete_listed=Delete Listed Items +title_delete_item=Delete Item +title_recover_item=Recover Item +title_recovery_report=Recovery Report +deleted_items=Deleted Items +manage_deleted_items=Manage Deleted Items +manage_deleted_items_description=Remove or recover previously deleted items +recover_all_items=Recover All Items +delete_all_items=Delete All Items +deleted_items_info=Use the Search to find deleted items by name or content and use the filters to reduce the list. +original_location=Original Location +deleted_date=Date Deleted +deleted_user=Deleted by User +recover=Recover +clear_search_results=Clear Search Results +search_deleted_items_name=Search by Name +search_deleted_items_text=Search by Content +deleted_items_for=for ''{0}'' +delete_item=Delete Item +delete_item_info=Permanently delete an item from the deleted file store +delete_item_confirm=Are you sure you want to permanently delete \"{0}\" from the deleted file store? The item cannot be recovered once this action has been performed. +recover_item=Recover Item +recover_item_info=Recover an item from the deleted file store +recover_item_confirm=Are you sure you want to recover \"{0}\" from the deleted file store? +delete_all_items_info=Permanently delete all files and spaces from the deleted file store +delete_all_items_confirm=Are you sure you want to permanently delete all files and spaces from the deleted file store? The items cannot be recovered once this action has been performed. +recover_all_items_info=Recover all files and spaces from the deleted file store +recover_all_items_confirm=Are you sure you want to recover all the deleted files and spaces from the deleted file store? +delete_listed_items=Delete Listed Items +delete_listed_items_info=Permanently delete the listed files and spaces from the deleted file store +delete_listed_items_confirm=Are you sure you want to permanently delete the following deleted files and spaces from the deleted file store? The items cannot be recovered once this action has been performed. +recover_listed_items=Recover Listed Items +recover_listed_items_info=Recover the listed files and spaces from the deleted file store +recover_listed_items_confirm=Are you sure you want to recover the following deleted files and spaces from the deleted file store? +recovered_item_success=The item \"{0}\" has been successfully recovered. +recovered_item_parent=Failed to recover the item \"{0}\" as the parent folder is missing, please select a new folder destination. +recovered_item_parent_short=Parent folder missing +recovered_item_permission=Failed to recover the item \"{0}\" as you do not have the appropriate permissions to restore the item to the parent folder, please select a new folder destination. +recovered_item_permission_short=No permissions to write +recovered_item_integrity=Failed to recover the item \"{0}\" as there is now an item in the parent folder with the same name, please select a new folder destination. +recovered_item_integrity_short=Item with same name exists +recovered_item_failure=Failed to recover the item \"{0}\" due to error: {1} +recovered_item_failure_short=Failed +delete_item_success=The item \"{0}\" has been permanently deleted. +title_deleted_item_details=Deleted Item Details +deleteditem_details_description=Details of the deleted item +alternative_destination=You may select a destination where you wish the recovered items to be placed. If you do not select a destination, the original location of the item is used. Recovery of an item may fail if the destination does not exist or you do not have permission to add items there. +user_filter_who=Who +user_filter_all=All +user_filter_user=User +date_filter_when=When +date_filter_all=All +date_filter_today=Today +date_filter_week=Last 7 days +date_filter_month=Last 30 days +recovery_report=Recovered Items Report +recovery_report_info=The results of recovering the selected items +recovery_report_success=The following items were recovered successfully +recovery_report_failed=The following items failed to recover +recovery_report_reason=Failure Reason + +# My Alfresco messages +my_alfresco=My Alfresco +title_my_alfresco=My Alfresco +dashboard_info=My Alfresco Dashboard +dashboard_description=Configure this view and build your personal Alfresco dashboard +configure=Configure +error_dashboard=An error occurred in one of the dashlets. + +# My Alfresco Layout Manager wizard messages +configure_dashboard_title=Configure Dashboard Wizard +configure_dashboard_desc=This wizard helps you configure your dashboard layout and contents +step_layout=Layout +configure_dashboard_step1_title=Step One - Select Layout +configure_dashboard_step1_desc=Choose the layout and number of columns for your dashboard. +step_columns=Components +configure_dashboard_step2_title=Step Two - Select Components +configure_dashboard_step2_desc=Select the components for your dashboard and add them to the columns. +configure_dashboard_finish_instruction=To save the dashboard configuration click Finish. To review or change your selections click Back. +select_layout=Select the style of layout for your dashboard. Changing your existing dashboard layout to another with less columns will result in the additional columns being removed. +select_column=Select the column to configure +dashlet_list=Available Components +dashlet_btn_select=Add +dashlet_btn_remove=Remove +selected_dashlets=Selected Components +dashboard_column=Column +column_max_components=this column can display {0} components + +# My Alfresco Layouts messages +layout_single_label=Single Column +layout_single_desc=This layout displays components in a single column the full width of the page +layout_narrow_left_label=Two Column Narrow Left +layout_narrow_left_desc=This layout display components in two columns with a narrow left hand column +layout_narrow_right_label=Two Column Narrow Right +layout_narrow_right_desc=This layout display components in two columns with a narrow right hand column +layout_three_column_label=Three Column +layout_three_column_desc=This layout displays components across three columns of equal width + +# My Alfresco Dashlet components messages +dashlet_gettingstarted_label=Getting Started +dashlet_gettingstarted_desc=Displays helpful information for getting started with the Alfresco web-client +dashlet_mydocuments=My Documents +dashlet_mydocuments_desc=Displays a list of the documents in my home space +dashlet_checkedoutdocs=My Checked Out Documents +dashlet_checkedoutdocs_desc=Displays a list of the documents checked out to me +dashlet_myimages=My Images +dashlet_myimages_desc=Displays a 3 column thumbnail list of the images in my home space +gettingstarted_commontasks=Common Tasks +gettingstarted_browse=Browse items in your home space +gettingstarted_browse_desc=From the toolbar click My Home to display a list of items in the space. +gettingstarted_createspace=Create a space in your home space +gettingstarted_createspace_desc=Navigate to your home space. In the space info area, from the menu under Create, click Create Space and enter information about the space. +gettingstarted_addcontent=Add content to your home space +gettingstarted_addcontent_desc=Navigate to your home space. In the space info area, click Add Content and enter information about the content to be uploaded. +gettingstarted_createcontent=Create content in your home space +gettingstarted_createcontent_desc=Navigate to your home space. In the space info area, click Create Content under Create and enter information about the content to be created. +gettingstarted_demonstration=Demonstration +gettingstarted_demonstration_desc=Watch a demonstration of how Alfresco can be used to create a marketing launch. +gettingstarted_featuretour=Feature Tour +gettingstarted_featuretour_desc=Take a tour of some of the key features in Alfresco. +gettingstarted_onlinehelp=Online Help +gettingstarted_onlinehelp_desc=Click the help icon to display Alfresco Help topics. + +# User Console and Settings messages +title_user_console=User Options +title_edit_user_details=Edit User Details +user_console=User Options +user_console_info=User Options +user_console_description=Use this page to change your options and settings +my_details=My Details +general_pref=General Preferences +change_my_password_description=Use this view to change your password +change_my_password_instructions=Enter your new password. +old_password=Old Password +new_password=New Password +edit_user_details=Edit User Details +edit_user_details_description=Use this view to change your user details and email address +start_location=Start Location + +# Delete Space Dialog messages +select_delete_operation=What do you want to delete? +delete_op_all=This space and all its contents. Note: Rules will also be deleted. +delete_op_files=Only the files within this space. +delete_op_folders=Only the folders within this space. +delete_op_contents=Files and folders within this space. + +# Email users dialog +email_space_users=Email Space users +email_space_users_desc=Send an email to the users and groups assigned to this space. + +# Workflow messages +doc_not_in_simple_workflow=This document is not part of a simple workflow. +space_not_in_simple_workflow=This space is not part of a simple workflow. +doc_not_in_advanced_workflow=This document is not part of any advanced workflows. +space_not_in_advanced_workflow=This space is not part of any advanced workflows. +simple_workflow=Simple Workflow +advanced_workflows=Advanced Workflows +document_action=The document will be {0} to ''{1}'' if the ''{2}'' action is taken. +space_action=The space will be {0} to ''{1}'' if the ''{2}'' action is taken. +doc_part_of_advanced_workflows=This document is part of the following advanced workflow(s) +space_part_of_advanced_workflows=This document is part of the following advanced workflow(s) +modify_workflow_props=Modify Properties of Simple Workflow +name_approve_step=Name for approve step +name_reject_step=Name for reject step +select_reject_step=Do you want to provide a reject step? +choose_copy_move_location=Choose whether you want to move or copy the content and also the location. +start_workflow=Start Advanced Workflow +start_workflow_wizard=Start Advanced Workflow Wizard +start_named_workflow_wizard=Start {0} Workflow Wizard +start_workflow_desc=This wizard helps you start an advanced workflow for an item in the repository. +available_workflows=Available workflows +step_choose_workflow=Choose Workflow +start_workflow_choose_title=Choose Workflow +start_workflow_choose_desc=Choose the workflow you want to start +step_workflow_options=Workflow Options +start_workflow_options_title=Workflow Options +start_workflow_options_desc=Select options for the workflow +start_workflow_finish_instruction=To start the workflow press Finish. To review or change your selections click Back. +start_workflow_no_metadata=There is no metadata to collect for this particular workflow. +start_workflow_no_workflows=There are no workflows currently deployed in the repository, press Cancel to exit the wizard. +users_and_roles=Users and their Roles +resources=Resources +manage_task=Manage Task +manage_task_title=Manage Task +manage_task_desc=This dialog allows the task to be managed. +view_completed_task_title=View Completed Task +view_completed_task_desc=This dialog allows the completed task details to be viewed. +task_properties=Task Properties +id=Id +status=Status +completed=Completed +source=Source +priority=Priority +my_tasks_todo_title=My Tasks To Do +my_tasks_todo_desc=List of your tasks still to complete +my_tasks_completed_title=My Completed Tasks +my_tasks_completed_desc=List of your completed tasks +due_date=Due Date +completed_on=Completed on +outcome=Outcome +reassign=Reassign +cancel_workflow=Cancel Workflow +cancel_workflow_info=To cancel this workflow, click OK. +cancel_workflow_confirm=Are you sure you want to cancel the \"{0}\" workflow? +error_cancel_workflow=Unable to cancel the workflow due to system error: +reassign_task_title=Reassign Task +reassign_task_desc=This dialog allows you to reassign a task. +reassign_select_user=Select the user to assign the task to, then press OK. +error_reassign_task=Unable to reassign the task due to system error: +part_of_workflow=Part of Workflow +initiated_by=Initiated by +started_on=Started on +add_resource=Add Resource +view_properties=View Content Properties +edit_properties=Edit Content Properties +save_changes=Save Changes +no_tasks=No tasks found. +no_resources=No resources found. +in_progress=In Progress +by=by + +# Workflow Definitions +wf_review_due_date=Review Due Date +wf_review_priority=Review Priority +wf_reviewer=Reviewer +wf_adhoc_due_date=Due Date +wf_adhoc_priority=Priority +wf_adhoc_assignee=Assign To + +# Admin Console messages +title_admin_console=Administration Console +admin_console=Administration Console +admin_description=Use this view to perform system administration functions. +admin_limited_license=Licensed: {0} license granted to {1} and limited to {3} days expiring {4,date,short} ({5} days remaining - issued on {2,date,short}). +admin_unlimited_license=Licensed: {0} license granted to {1} and does not expire (issued on {2,date,short}). +admin_invalid_license=Licensed: LICENSE INVALID - Alfresco Repository restricted to read-only capability. + +# UI Page Titles +title_about=About Alfresco +title_login=Alfresco Web Client - Login +title_relogin=Alfresco Web Client - Logged Out +title_error=Alfresco Web Client - System Error +title_browse=Alfresco Web Client +title_inviteusers_invite=Invite Users - Invite +title_inviteusers_notify=Invite Users - Notify +title_invitecontentusers_invite=Invite Content Users - Invite +title_invitecontentusers_notify=Invite Content Users - Notify +title_change_user_roles=Change User Roles +title_remove_invited_user=Remove Invited User +title_advanced_search=Advanced Search +title_checkin_file=Check In File +title_checkout_file=Check Out File +title_checkout_file_link=Check Out File Download +title_delete_file=Delete File +title_delete_rule=Delete Rule +title_delete_user=Delete User +title_delete_space=Delete Space +title_file_details=Document Details +title_file_preview=Preview In Template +title_edit_categories=Edit Categories +title_edit_doc_props=Edit Document Properties +title_edit_link_props=Edit Link Properties +title_edit_file=Edit File +title_edit_html_inline=Edit HTML File Inline +title_edit_text_inline=Edit Text File Inline +title_edit_simple_workflow=Edit Simple Workflow +title_edit_space=Edit Space Details +title_rules=Space Rules +title_space_details=Space Details +title_apply_template=Apply Template +title_system_info=System Information +title_undo_checkout=Undo Check Out +title_update_file=Update File Content +title_users=User Management +title_invited_users=Manage Invited Users +title_content_users=Manage Content Users +title_add_content=Add Content +title_create_content=Create New Content +title_create_content_props=Create New Content - Properties +title_create_content_summary=Create New Content - Summary +title_action_add_feature=Add Feature Action +title_action_remove_feature=Remove Feature Action +title_action_checkin=Check In Action +title_action_checkout=Check Out Action +title_action_copy=Copy Action +title_action_move=Move Action +title_action_script=Script Action +title_action_import=Import Action +title_action_email=Email Action +title_action_link_category=Link Category Action +title_action_simple_workflow=Simple Workflow Action +title_action_transform=Transform Action +title_action_transform_image=Transform Image Action +title_action_specialise_type=Specialise Type Action +title_condition_contains_text=Contains Text Condition +title_condition_has_mimetype=Has Mimetype Condition +title_condition_in_category=In Category Condition +title_condition_is_subtype=Is Subtype Condition +title_condition_has_aspect=Has Aspect Condition +title_new_user_person_props=User - Person Properties +title_new_user_user_props=User - User Properties +title_new_user_summary=User - Summary +title_export=Export +title_import=Import +title_admin_store_browser=Alfresco Store Browser +title_admin_node_browser=Alfresco Node Browser +title_admin_search_results=Node Browser Search Results +title_forums=Forum Space +title_forum=Forum +title_topic=Topic +title_delete_forums_space=Delete Forum Space +title_delete_forum_space=Delete Forum +title_delete_topic_space=Delete Topic +title_delete_post=Delete Post +title_create_forums=Create Forum Space +title_create_forum=Create Forum +title_forums_details=Forum Space Details +title_forum_details=Forum Details +title_create_topic=Create Topic +title_create_discussion=Create Discussion +title_topic_details=Topic Details +title_create_post=Post Message +title_create_reply=Create Reply +title_edit_forums=Edit Forum Space Details +title_edit_forum=Edit Forum Details +title_edit_topic=Edit Topic Details +title_edit_post=Edit Post +title_noaccess=No Access + +# UI Error messages +error_generic=A system error happened during the operation: {0} +error_noderef=Unable to find the repository item referenced by Id: {0} - the record has probably been deleted from the database. +error_deleted_folder=The folder item referenced by Id: {0} - has been deleted from the database. The system has changed your folder location as the folder you were in no longer exists. +error_homespace=The Home Space node referenced by Id: {0} cannot be found. It may have been deleted from the database. Please contact your system administrator. +error_search=Search failed due to system error: {0} +error_exists=A Space or File with that name already exists: {0} +error_delete_space=Unable to delete Space due to system error: +error_delete_file=Unable to delete File due to system error: +error_checkout=Unable to check out Content Node due to system error: +error_update=Unable to update Content Node due to system error: +error_cancel_checkout=Unable to cancel check out of Content Node due to system error: +error_checkin=Unable to check in Content Node due to system error: +error_paste=Unable to paste item due to system error: +error_login_user=Unable to login - unknown username/password. +error_login_missing=Must specify username and password. +error_delete_rule=Unable to delete Rule due to system error: +error_actions=Failed to run Actions due to error: {0} +error_rule=Failed to create Rule due to error: {0} +error_space=Failed to create new space due to error: {0} +error_person=Failed to create Person due to error: {0} +error_delete_user=Failed to delete User due to error: {0} +error_delete_user_object=Unable to delete the User object associated with the Person. This is not an error if an external authentication mechanism such as NTLM was previously active. +error_remove_user=Failed to remove User due to error: {0} +error_password_match=Please ensure that both password fields contain the same value. +error_property=Property ''{0}'' is not available for this node +error_create_space_dialog=Please correct the errors below then click Create Space. +error_create_forums_dialog=Please correct the errors below then click Create Forum Space. +error_create_forum_dialog=Please correct the errors below then click Create Forum. +error_create_topic_dialog=Please correct the errors below then click Create Topic. +error_create_post_dialog=Please correct the errors below then click Post. +error_create_reply_dialog=Please correct the errors below then click Reply. +error_create_category_dialog=Please correct the errors below then click New Category. +error_create_group_dialog=Please correct the errors below then click Create Group. +error_dialog=Please correct the errors below then click OK. +error_wizard=Please correct the errors below then click Finish. +error_update_category=Failed to update category due to system error: {0} +error_update_simpleworkflow=Failed to update simple workflow due to system error: {0} +error_workflow_approve=Failed to approve the document due to system error: {0} +error_workflow_reject=Failed to reject the document due to system error: {0} +error_aspect_classify=Failed to apply the ''classifiable'' aspect to the document due to system error: {0} +error_aspect_classify_space=Failed to apply the ''classifiable'' aspect to the space due to system error: {0} +error_aspect_versioning=Failed to apply the ''versionable'' aspect to the document due to system error: {0} +error_aspect_inlineeditable=Failed to apply the ''inlineeditable'' aspect to the document due to system error: {0} +error_content_missing=The node''s content is missing: \n node: {0} \n reader: {1} \nPlease contact your system administrator. +error_export=Failed to execute export: {0} +error_import=Failed to execute import: {0} +error_import_no_file=Can not find an ACP file to import! +error_import_empty_file=You can not import an empty ACP file! +error_import_all=Please correct the import errors below then click OK. +error_export_all=Please correct the export errors below then click OK. +error_save_search=Failed to save search due to error: {0} +error_restore_search=Failed to restore saved search due to error: {0} +error_shortcut_permissions=Unable to navigate to the item as it cannot be read by this user. Another user may have modified the permission. + +# Confirmations +return_to_application=Return to application +delete_space_info=To remove this space and all of its contents, click OK. +delete_space_confirm=Are you sure you want to delete \"{0}\" and all its contents? +delete_forums_info=To remove this forum space and its contents, click OK. +delete_forum_info=To remove this forum and its topics, click OK. +delete_forum_confirm=Are you sure you want to delete \"{0}\" and all its topics? +delete_topic_info=To remove this topic and its posts, click OK. +delete_topic_confirm=Are you sure you want to delete \"{0}\" and all its posts? +delete_post_info=To remove this post from the topic, click OK. +delete_post_confirm=Are you sure you want to delete the post from \"{0}\"? +delete_file_info=To remove this file and any previous versions, click OK. +delete_file_confirm=Are you sure you want to delete \"{0}\" and all previous versions? +delete_rule_info=To remove this rule from the space, click Yes. +delete_user_info=To delete this user from the system, click Yes. +delete_rule_confirm=Are you sure you want to delete \"{0}\"? +delete_user_confirm=The User will no longer be able to access the system. Are you sure you want to delete user \"{0}\"? +remove_invited_user_confirm=The User will no longer be able to access the documents and folders in this space. Are you sure you want to remove user \"{0}\"? +remove_content_user_confirm=The User will no longer be able to access this content. Are you sure you want to remove user \"{0}\"? +delete_companyroot_confirm=WARNING: This folder is a special folder accessed by all Users! Please be sure that you wish to delete this folder. It may cause System Errors if you remove it. + +# Status Messages +status_space_created=Successfully created space ''{0}''. +status_space_deleted=Successfully deleted space ''{0}''. +status_space_updated=Successfully updated space ''{0}''. + +# Validation Messages +validation_mandatory={0} is a mandatory field. +validation_string_length={0} must be between {1} and {2} characters in length. +validation_is_number={0} must contain a number. +validation_regex={0} is invalid. +validation_regex_not_match={0} is invalid. +validation_numeric_range={0} must be between {1} and {2}. +validation_invalid_character=is an invalid character. \ No newline at end of file diff --git a/config/alfresco/web-client-application-context.xml b/config/alfresco/web-client-application-context.xml index 1c03c8494c..e46404a42a 100644 --- a/config/alfresco/web-client-application-context.xml +++ b/config/alfresco/web-client-application-context.xml @@ -12,7 +12,6 @@ classpath:alfresco/web-client-config-properties.xml classpath:alfresco/web-client-config-navigation.xml classpath:alfresco/web-client-config-actions.xml - classpath:alfresco/web-client-config-workflow.xml classpath:alfresco/web-client-config-forum-actions.xml classpath:alfresco/web-client-config-wcm-actions.xml classpath:alfresco/web-client-config-workflow-actions.xml diff --git a/config/alfresco/web-client-config-actions.xml b/config/alfresco/web-client-config-actions.xml index d8bffa8ead..cefde01e6a 100644 --- a/config/alfresco/web-client-config-actions.xml +++ b/config/alfresco/web-client-config-actions.xml @@ -254,6 +254,54 @@ + + + org.alfresco.web.action.evaluator.ApproveDocEvaluator + + /images/icons/approve.gif + #{SpaceDetailsBean.approve} + browse + + #{actionContext.id} + + + + + + org.alfresco.web.action.evaluator.RejectDocEvaluator + + /images/icons/reject.gif + #{SpaceDetailsBean.reject} + browse + + #{actionContext.id} + + + + + + org.alfresco.web.action.evaluator.ApproveDocEvaluator + + /images/icons/approve.gif + #{SpaceDetailsBean.approve} + browse + + #{actionContext.id} + + + + + + org.alfresco.web.action.evaluator.RejectDocEvaluator + + /images/icons/reject.gif + #{SpaceDetailsBean.reject} + browse + + #{actionContext.id} + + + @@ -313,7 +361,7 @@ /images/icons/paste.gif #{ClipboardBean.pasteAll} - + @@ -344,7 +392,7 @@ - CreateChildren + ChangePermissions advanced_space_wizard /images/icons/create_space.gif @@ -376,7 +424,7 @@ - Write + CreateChildren manage_rules /images/icons/rule.gif @@ -570,6 +618,8 @@ + + @@ -620,6 +670,8 @@ + + diff --git a/config/alfresco/web-client-config-dialogs.xml b/config/alfresco/web-client-config-dialogs.xml index 318351342c..d48f1610bc 100644 --- a/config/alfresco/web-client-config-dialogs.xml +++ b/config/alfresco/web-client-config-dialogs.xml @@ -25,18 +25,18 @@ diff --git a/config/alfresco/web-client-config-properties.xml b/config/alfresco/web-client-config-properties.xml index 20b4c73d4c..84c9cde9b5 100644 --- a/config/alfresco/web-client-config-properties.xml +++ b/config/alfresco/web-client-config-properties.xml @@ -183,130 +183,18 @@ + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -324,7 +212,7 @@ - + @@ -372,9 +260,21 @@ + + + - - + + + + + + + + + + + @@ -384,19 +284,21 @@ + + + - - - - + + + - + @@ -405,12 +307,12 @@ - - - + + + - - + + @@ -418,10 +320,10 @@ - + + + - - @@ -429,9 +331,9 @@ - - - + + + diff --git a/config/alfresco/web-client-config-wizards.xml b/config/alfresco/web-client-config-wizards.xml index 9299b4b55d..3e459f34b8 100644 --- a/config/alfresco/web-client-config-wizards.xml +++ b/config/alfresco/web-client-config-wizards.xml @@ -277,58 +277,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + description-id="start_workflow_desc" icon="/images/icons/new_workflow_large.gif"> + + manage_task + /images/icons/manage_workflow_task.gif + dialog:manageTask + #{DialogManager.setupParameters} + + #{actionContext.id} + + + + + view_completed_task_title + /images/icons/view_workflow_task.gif + dialog:viewCompletedTask + #{DialogManager.setupParameters} + + #{actionContext.id} + + + reassign /images/icons/reassign_task.gif dialog:reassignTask #{DialogManager.setupParameters} - #{actionContext.id} + #{actionContext.id} cancel_workflow /images/icons/cancel_workflow.gif + org.alfresco.web.action.evaluator.CancelWorkflowEvaluator dialog:cancelWorkflow #{DialogManager.setupParameters} @@ -51,7 +72,7 @@ view_properties - /images/icons/View_details.gif + /images/icons/view_properties.gif dialog:viewContentProperties #{BrowseBean.setupContentAction} @@ -64,7 +85,7 @@ Write edit_properties - /images/icons/Change_details.gif + /images/icons/edit_properties.gif dialog:editContentProperties #{BrowseBean.setupContentAction} @@ -72,6 +93,45 @@ + + + org.alfresco.web.action.evaluator.CheckinDocEvaluator + checkin + /images/icons/CheckIn_icon.gif + #{CheckinCheckoutBean.setupWorkflowContentAction} + dialog:checkinFile + + #{actionContext.id} + #{actionContext.taskId} + + + + + + org.alfresco.web.action.evaluator.CheckoutDocEvaluator + checkout + /images/icons/CheckOut_icon.gif + #{CheckinCheckoutBean.setupWorkflowContentAction} + dialog:checkoutFile + + #{actionContext.id} + #{actionContext.taskId} + + + + + + org.alfresco.web.action.evaluator.CancelCheckoutDocEvaluator + undocheckout + /images/icons/undo_checkout.gif + #{CheckinCheckoutBean.setupWorkflowContentAction} + dialog:undoCheckoutFile + + #{actionContext.id} + #{actionContext.taskId} + + + @@ -81,10 +141,12 @@ + + @@ -104,9 +166,9 @@ - - - + + + diff --git a/project-build.xml b/project-build.xml index 47122a656e..80d3d01658 100644 --- a/project-build.xml +++ b/project-build.xml @@ -20,7 +20,7 @@ isTomcat = ${isTomcat} isJBoss = ${isJBoss} - + diff --git a/source/java/org/alfresco/web/action/evaluator/ApproveDocEvaluator.java b/source/java/org/alfresco/web/action/evaluator/ApproveDocEvaluator.java index caf3a15cd6..162653efc7 100644 --- a/source/java/org/alfresco/web/action/evaluator/ApproveDocEvaluator.java +++ b/source/java/org/alfresco/web/action/evaluator/ApproveDocEvaluator.java @@ -16,13 +16,11 @@ */ package org.alfresco.web.action.evaluator; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.web.action.ActionEvaluator; import org.alfresco.web.bean.repository.Node; /** - * UI Action Evaluator - 'Approve' workflow step for document. + * UI Action Evaluator - 'Approve' workflow step for document or space. * * @author Kevin Roast */ diff --git a/source/java/org/alfresco/web/action/evaluator/CancelWorkflowEvaluator.java b/source/java/org/alfresco/web/action/evaluator/CancelWorkflowEvaluator.java new file mode 100644 index 0000000000..cdefed2495 --- /dev/null +++ b/source/java/org/alfresco/web/action/evaluator/CancelWorkflowEvaluator.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.action.evaluator; + +import javax.faces.context.FacesContext; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.util.ISO9075; +import org.alfresco.web.action.ActionEvaluator; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; + +/** + * UI Action Evaluator for cancel workflow action. The action + * is only allowed if the workflow the task belongs to was + * started by the current user. + * + * @author gavinc + */ +public class CancelWorkflowEvaluator implements ActionEvaluator +{ + /** + * @see org.alfresco.web.action.ActionEvaluator#evaluate(org.alfresco.web.bean.repository.Node) + */ + public boolean evaluate(Node node) + { + boolean result = false; + + // get the id of the task + String taskId = (String)node.getProperties().get("id"); + if (taskId != null) + { + FacesContext context = FacesContext.getCurrentInstance(); + + // get the initiator of the workflow the task belongs to + WorkflowService workflowSvc = Repository.getServiceRegistry( + context).getWorkflowService(); + + WorkflowTask task = workflowSvc.getTaskById(taskId); + if (task != null) + { + NodeRef initiator = task.path.instance.initiator; + if (initiator != null) + { + // find the current username + User user = Application.getCurrentUser(context); + String currentUserName = ISO9075.encode(user.getUserName()); + + // get the username of the initiator + NodeService nodeSvc = Repository.getServiceRegistry( + context).getNodeService(); + String userName = (String)nodeSvc.getProperty(initiator, ContentModel.PROP_USERNAME); + + // if the current user started the workflow allow the cancel action + if (currentUserName.equals(userName)) + { + result = true; + } + } + } + } + + return result; + } +} diff --git a/source/java/org/alfresco/web/action/evaluator/RejectDocEvaluator.java b/source/java/org/alfresco/web/action/evaluator/RejectDocEvaluator.java index 17489475ff..687cb73df0 100644 --- a/source/java/org/alfresco/web/action/evaluator/RejectDocEvaluator.java +++ b/source/java/org/alfresco/web/action/evaluator/RejectDocEvaluator.java @@ -16,13 +16,11 @@ */ package org.alfresco.web.action.evaluator; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.web.action.ActionEvaluator; import org.alfresco.web.bean.repository.Node; /** - * UI Action Evaluator - 'Reject' workflow step for document. + * UI Action Evaluator - 'Reject' workflow step for document or space. * * @author Kevin Roast */ diff --git a/source/java/org/alfresco/web/app/Application.java b/source/java/org/alfresco/web/app/Application.java index d2623ec4b0..0d3f7f25b3 100644 --- a/source/java/org/alfresco/web/app/Application.java +++ b/source/java/org/alfresco/web/app/Application.java @@ -1,960 +1,960 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.web.app; - -import java.io.IOException; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.ResourceBundle; -import java.util.StringTokenizer; - -import javax.faces.context.FacesContext; -import javax.portlet.PortletContext; -import javax.portlet.PortletSession; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.alfresco.config.ConfigService; -import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.web.app.servlet.AuthenticationHelper; -import org.alfresco.web.app.servlet.FacesHelper; -import org.alfresco.web.bean.ErrorBean; -import org.alfresco.web.bean.dashboard.DashboardManager; -import org.alfresco.web.bean.dialog.DialogManager; -import org.alfresco.web.bean.repository.User; -import org.alfresco.web.bean.wizard.WizardManager; -import org.alfresco.web.config.ClientConfigElement; -import org.apache.commons.logging.Log; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.web.jsf.FacesContextUtils; - -/** - * Utilities class - * - * @author gavinc - */ -public class Application -{ - private static final String LOCALE = "locale"; - - public static final String BEAN_CONFIG_SERVICE = "webClientConfigService"; - public static final String BEAN_DATA_DICTIONARY = "dataDictionary"; - public static final String BEAN_IMPORTER_BOOTSTRAP = "spacesBootstrap"; - - public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; - - private static boolean inPortalServer = false; - private static StoreRef repoStoreRef; - private static String rootPath; - private static String companyRootId; - private static String glossaryFolderName; - private static String spaceTemplatesFolderName; - private static String contentTemplatesFolderName; - private static String emailTemplatesFolderName; - private static String rssTemplatesFolderName; - private static String savedSearchesFolderName; - private static String scriptsFolderName; - private static String guestHomeFolderName; - private static String websitesFolderName; - private static String contentFormsFolderName; - - /** - * Private constructor to prevent instantiation of this class - */ - private Application() - { - } - - /** - * Sets whether this application is running inside a portal server - * - * @param inPortal true to indicate the application is running as a portlet - */ - public static void setInPortalServer(boolean inPortal) - { - inPortalServer = inPortal; - } - - /** - * Determines whether the server is running in a portal - * - * @return true if we are running inside a portal server - */ - public static boolean inPortalServer() - { - return inPortalServer; - } - - /** - * Handles errors thrown from servlets - * - * @param servletContext The servlet context - * @param request The HTTP request - * @param response The HTTP response - * @param error The exception - * @param logger The logger - */ - public static void handleServletError(ServletContext servletContext, HttpServletRequest request, - HttpServletResponse response, Throwable error, Log logger, String returnPage) - throws IOException, ServletException - { - // get the error bean from the session and set the error that occurred. - HttpSession session = request.getSession(); - ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME); - if (errorBean == null) - { - errorBean = new ErrorBean(); - session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean); - } - errorBean.setLastError(error); - errorBean.setReturnPage(returnPage); - - // try and find the configured error page - boolean errorShown = false; - String errorPage = getErrorPage(servletContext); - - if (errorPage != null) - { - if (logger.isDebugEnabled()) - logger.debug("An error has occurred, redirecting to error page: " + errorPage); - - if (response.isCommitted() == false) - { - errorShown = true; - response.sendRedirect(request.getContextPath() + errorPage); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Response is already committed, re-throwing error"); - } - } - else - { - if (logger.isDebugEnabled()) - logger.debug("No error page defined, re-throwing error"); - } - - // if we could not show the error page for whatever reason, re-throw the error - if (!errorShown) - { - if (error instanceof IOException) - { - throw (IOException)error; - } - else if (error instanceof ServletException) - { - throw (ServletException)error; - } - else - { - throw new ServletException(error); - } - } - } - - /** - * Retrieves the DialogManager managed bean - * - * @return DialogManager bean - */ - public static DialogManager getDialogManager() - { - return (DialogManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "DialogManager"); - } - - /** - * Retrieves the WizardManager managed bean - * - * @return WizardManager bean - */ - public static WizardManager getWizardManager() - { - return (WizardManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "WizardManager"); - } - - /** - * Retrieves the DashboardManager managed bean - * - * @return DashboardManager bean - */ - public static DashboardManager getDashboardManager() - { - return (DashboardManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "DashboardManager"); - } - - /** - * Retrieves the configured error page for the application - * - * @param servletContext The servlet context - * @return The configured error page or null if the configuration is missing - */ - public static String getErrorPage(ServletContext servletContext) - { - return getErrorPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); - } - - /** - * Retrieves the configured error page for the application - * - * @param portletContext The portlet context - * @return - */ - public static String getErrorPage(PortletContext portletContext) - { - return getErrorPage((WebApplicationContext)portletContext.getAttribute( - WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); - } - - /** - * Retrieves the configured login page for the application - * - * @param servletContext The servlet context - * @return The configured login page or null if the configuration is missing - */ - public static String getLoginPage(ServletContext servletContext) - { - return getLoginPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); - } - - /** - * Retrieves the configured login page for the application - * - * @param portletContext The portlet context - * @return - */ - public static String getLoginPage(PortletContext portletContext) - { - return getLoginPage((WebApplicationContext)portletContext.getAttribute( - WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); - } - - /** - * @return Returns the User object representing the currently logged in user - */ - public static User getCurrentUser(HttpSession session) - { - return (User)session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); - } - - /** - * @return Returns the User object representing the currently logged in user - */ - public static User getCurrentUser(FacesContext context) - { - return (User)context.getExternalContext().getSessionMap().get(AuthenticationHelper.AUTHENTICATION_USER); - } - - /** - * @return Returns the repository store URL (retrieved from config service) - */ - public static StoreRef getRepositoryStoreRef(ServletContext context) - { - return getRepositoryStoreRef(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the repository store URL (retrieved from config service) - */ - public static StoreRef getRepositoryStoreRef(FacesContext context) - { - return getRepositoryStoreRef(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns id of the company root - */ - public static String getCompanyRootId() - { - return companyRootId; - } - - /** - * Sets the company root id. This is setup by the ContextListener. - * - * @param id The company root id - */ - public static void setCompanyRootId(String id) - { - companyRootId = id; - } - - /** - * @return Returns the root path for the application - */ - public static String getRootPath(ServletContext context) - { - return getRootPath(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the root path for the application - */ - public static String getRootPath(FacesContext context) - { - return getRootPath(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the glossary folder name - */ - public static String getGlossaryFolderName(ServletContext context) - { - return getGlossaryFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the glossary folder name - */ - public static String getGlossaryFolderName(FacesContext context) - { - return getGlossaryFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Space templates folder name - */ - public static String getSpaceTemplatesFolderName(ServletContext context) - { - return getSpaceTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Space templates folder name - */ - public static String getSpaceTemplatesFolderName(FacesContext context) - { - return getSpaceTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Content templates folder name - */ - public static String getContentTemplatesFolderName(ServletContext context) - { - return getContentTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Content templates folder name - */ - public static String getContentTemplatesFolderName(FacesContext context) - { - return getContentTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Email templates folder name - */ - public static String getEmailTemplatesFolderName(ServletContext context) - { - return getEmailTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the Email templates folder name - */ - public static String getEmailTemplatesFolderName(FacesContext context) - { - return getEmailTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the RSS templates folder name - */ - public static String getRSSTemplatesFolderName(ServletContext context) - { - return getRSSTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Returns the RSS templates folder name - */ - public static String getRSSTemplatesFolderName(FacesContext context) - { - return getRSSTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the Saved Searches folder name - */ - public static String getSavedSearchesFolderName(ServletContext context) - { - return getSavedSearchesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the Saved Searches folder name - */ - public static String getSavedSearchesFolderName(FacesContext context) - { - return getSavedSearchesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the JavaScript scripts folder name - */ - public static String getScriptsFolderName(ServletContext context) - { - return getScriptsFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the JavaScript scripts folder name - */ - public static String getScriptsFolderName(FacesContext context) - { - return getScriptsFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the Guest Home folder name - */ - public static String getGuestHomeFolderName(ServletContext context) - { - return getGuestHomeFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return Return the Guest Home folder name - */ - public static String getGuestHomeFolderName(FacesContext context) - { - return getGuestHomeFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return the Websites folder name - */ - public static String getWebsitesFolderName(ServletContext context) - { - return getWebsitesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return the Websites folder name - */ - public static String getWebsitesFolderName(FacesContext context) - { - return getWebsitesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return the WCM Content Forms folder name - */ - public static String getContentFormsFolderName(ServletContext context) - { - return getContentFormsFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * @return the WCM Content Forms folder name - */ - public static String getContentFormsFolderName(FacesContext context) - { - return getContentFormsFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); - } - - /** - * Set the language locale for the current user context - * - * @param context FacesContext for current user - * @param code The ISO locale code to set - */ - public static void setLanguage(FacesContext context, String code) - { - Locale locale = parseLocale(code); - - // set locale for JSF framework usage - context.getViewRoot().setLocale(locale); - - // set locale for our framework usage - context.getExternalContext().getSessionMap().put(LOCALE, locale); - - // clear the current message bundle - so it's reloaded with new locale - context.getExternalContext().getSessionMap().remove(MESSAGE_BUNDLE); - } - - /** - * Set the language locale for the current user session - * - * @param session HttpSession for current user - * @param code The ISO locale code to set - */ - public static void setLanguage(HttpSession session, String code) - { - Locale locale = parseLocale(code); - - session.putValue(LOCALE, locale); - session.removeAttribute(MESSAGE_BUNDLE); - } - - /** - * @param code Locale code (java format with underscores) to parse - * @return Locale object or default if unable to parse - */ - private static Locale parseLocale(String code) - { - Locale locale = Locale.getDefault(); - - StringTokenizer t = new StringTokenizer(code, "_"); - int tokens = t.countTokens(); - if (tokens == 1) - { - locale = new Locale(code); - } - else if (tokens == 2) - { - locale = new Locale(t.nextToken(), t.nextToken()); - } - else if (tokens == 3) - { - locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken()); - } - - return locale; - } - - /** - * Return the language Locale for the current user context - * - * @param context FacesContext for the current user - * - * @return Current language Locale set or the VM default if none set - */ - public static Locale getLanguage(FacesContext context) - { - Locale locale = (Locale)context.getExternalContext().getSessionMap().get(LOCALE); - return locale != null ? locale : Locale.getDefault(); - } - - /** - * Return the language Locale for the current user Session. - * - * @param session HttpSession for the current user - * - * @return Current language Locale set or the VM default if none set - */ - public static Locale getLanguage(HttpSession session) - { - Locale locale = (Locale)session.getAttribute(LOCALE); - return locale != null ? locale : Locale.getDefault(); - } - - /** - * Return the language Locale for the current user PortletSession. - * - * @param session PortletSession for the current user - * - * @return Current language Locale set or the VM default if none set - */ - public static Locale getLanguage(PortletSession session) - { - Locale locale = (Locale)session.getAttribute(LOCALE); - return locale != null ? locale : Locale.getDefault(); - } - - /** - * Get the specified I18N message string from the default message bundle for this user - * - * @param context FacesContext - * @param msg Message ID - * - * @return String from message bundle or $$msg$$ if not found - */ - public static String getMessage(FacesContext context, String msg) - { - return getBundle(context).getString(msg); - } - - /** - * Get the specified I18N message string from the default message bundle for this user - * - * @param session HttpSession - * @param msg Message ID - * - * @return String from message bundle or $$msg$$ if not found - */ - public static String getMessage(HttpSession session, String msg) - { - return getBundle(session).getString(msg); - } - - /** - * Get the specified the default message bundle for this user - * - * @param session HttpSession - * - * @return ResourceBundle for this user - */ - public static ResourceBundle getBundle(HttpSession session) - { - ResourceBundle bundle = (ResourceBundle)session.getAttribute(MESSAGE_BUNDLE); - if (bundle == null) - { - // get Locale from language selected by each user on login - Locale locale = (Locale)session.getAttribute(LOCALE); - if (locale == null) - { - locale = Locale.getDefault(); - } - bundle = ResourceBundleWrapper.getResourceBundle(MESSAGE_BUNDLE, locale); - - session.setAttribute(MESSAGE_BUNDLE, bundle); - } - - return bundle; - } - - /** - * Get the specified the default message bundle for this user - * - * @param context FacesContext - * - * @return ResourceBundle for this user - */ - public static ResourceBundle getBundle(FacesContext context) - { - // get the resource bundle for the current locale - // we store the bundle in the users session - // this makes it easy to add a locale per user support later - Map session = context.getExternalContext().getSessionMap(); - ResourceBundle bundle = (ResourceBundle)session.get(MESSAGE_BUNDLE); - if (bundle == null) - { - // get Locale from language selected by each user on login - Locale locale = (Locale)session.get(LOCALE); - if (locale == null) - { - locale = Locale.getDefault(); - } - bundle = ResourceBundleWrapper.getResourceBundle(MESSAGE_BUNDLE, locale); - - session.put(MESSAGE_BUNDLE, bundle); - } - - return bundle; - } - - /** - * Helper to get the ConfigService instance - * - * @param context FacesContext - * - * @return ConfigService - */ - public static ConfigService getConfigService(FacesContext context) - { - return (ConfigService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean( - Application.BEAN_CONFIG_SERVICE); - } - - /** - * Helper to get the ConfigService instance - * - * @param context ServletContext - * - * @return ConfigService - */ - public static ConfigService getConfigService(ServletContext context) - { - return (ConfigService)WebApplicationContextUtils.getRequiredWebApplicationContext(context).getBean( - Application.BEAN_CONFIG_SERVICE); - } - - /** - * Helper to get the client config element from the config service - * - * @param context FacesContext - * @return The ClientConfigElement - */ - public static ClientConfigElement getClientConfig(FacesContext context) - { - return (ClientConfigElement)getConfigService(context).getGlobalConfig(). - getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); - } - - /** - * Returns the repository store URL - * - * @param context The spring context - * @return The repository store URL to use - */ - private static StoreRef getRepositoryStoreRef(WebApplicationContext context) - { - if (repoStoreRef == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - repoStoreRef = bootstrap.getStoreRef(); - } - - return repoStoreRef; - } - - /** - * Returns the root path for the application - * - * @param context The spring context - * @return The application root path - */ - private static String getRootPath(WebApplicationContext context) - { - if (rootPath == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - rootPath = configuration.getProperty("spaces.company_home.childname"); - } - - return rootPath; - } - - /** - * Returns the glossary folder name - * - * @param context The spring context - * @return The glossary folder name - */ - private static String getGlossaryFolderName(WebApplicationContext context) - { - if (glossaryFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - glossaryFolderName = configuration.getProperty("spaces.dictionary.childname"); - } - - return glossaryFolderName; - } - - /** - * Returns the Space Templates folder name - * - * @param context The spring context - * @return The templates folder name - */ - private static String getSpaceTemplatesFolderName(WebApplicationContext context) - { - if (spaceTemplatesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - spaceTemplatesFolderName = configuration.getProperty("spaces.templates.childname"); - } - - return spaceTemplatesFolderName; - } - - /** - * Returns the Content Templates folder name - * - * @param context The spring context - * @return The templates folder name - */ - private static String getContentTemplatesFolderName(WebApplicationContext context) - { - if (contentTemplatesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - contentTemplatesFolderName = configuration.getProperty("spaces.templates.content.childname"); - } - - return contentTemplatesFolderName; - } - - /** - * Returns the Email Templates folder name - * - * @param context The spring context - * @return The email folder name - */ - private static String getEmailTemplatesFolderName(WebApplicationContext context) - { - if (emailTemplatesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - emailTemplatesFolderName = configuration.getProperty("spaces.templates.email.childname"); - } - - return emailTemplatesFolderName; - } - - /** - * Returns the RSS Templates folder name - * - * @param context The spring context - * @return The RSS folder name - */ - private static String getRSSTemplatesFolderName(WebApplicationContext context) - { - if (rssTemplatesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - rssTemplatesFolderName = configuration.getProperty("spaces.templates.rss.childname"); - } - - return rssTemplatesFolderName; - } - - /** - * Returns the Saved Searches folder name - * - * @param context The spring context - * @return The saved searches folder name - */ - private static String getSavedSearchesFolderName(WebApplicationContext context) - { - /* - * This lookup is duplicated in a patch. If this logic changes, investigate the changes - * required for the patch(es). - */ - - if (savedSearchesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - savedSearchesFolderName = configuration.getProperty("spaces.savedsearches.childname"); - } - - return savedSearchesFolderName; - } - - /** - * Returns the JavaScript scripts folder name - * - * @param context The spring context - * @return The scripts folder name - */ - private static String getScriptsFolderName(WebApplicationContext context) - { - if (scriptsFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - scriptsFolderName = configuration.getProperty("spaces.scripts.childname"); - } - - return scriptsFolderName; - } - - /** - * Returns the Guest Home folder name - * - * @param context The spring context - * @return The Guest Home folder name - */ - private static String getGuestHomeFolderName(WebApplicationContext context) - { - if (guestHomeFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - guestHomeFolderName = configuration.getProperty("spaces.guest_home.childname"); - } - - return guestHomeFolderName; - } - - /** - * Returns the Websites folder name - * - * @param context The Spring context - * @return The Websites folder name - */ - private static String getWebsitesFolderName(WebApplicationContext context) - { - if (websitesFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - websitesFolderName = configuration.getProperty("spaces.wcm.childname"); - } - - return websitesFolderName; - } - - /** - * Returns the WCM Content Forms folder name - * - * @param context The Spring context - * @return The WCM Content Forms folder name - */ - private static String getContentFormsFolderName(WebApplicationContext context) - { - if (contentFormsFolderName == null) - { - ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); - Properties configuration = bootstrap.getConfiguration(); - contentFormsFolderName = configuration.getProperty("spaces.content_forms.childname"); - } - - return contentFormsFolderName; - } - - /** - * Retrieves the configured error page for the application - * - * @param context The Spring context - * @return The configured error page or null if the configuration is missing - */ - private static String getErrorPage(WebApplicationContext context) - { - String errorPage = null; - - ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); - ClientConfigElement clientConfig = (ClientConfigElement)svc.getGlobalConfig(). - getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); - - if (clientConfig != null) - { - errorPage = clientConfig.getErrorPage(); - } - - return errorPage; - } - - /** - * Retrieves the configured login page for the application - * - * @param context The Spring contexr - * @return The configured login page or null if the configuration is missing - */ - private static String getLoginPage(WebApplicationContext context) - { - String loginPage = null; - - ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); - ClientConfigElement clientConfig = (ClientConfigElement)svc.getGlobalConfig(). - getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); - - if (clientConfig != null) - { - loginPage = clientConfig.getLoginPage(); - } - - return loginPage; - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import javax.faces.context.FacesContext; +import javax.portlet.PortletContext; +import javax.portlet.PortletSession; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.alfresco.config.ConfigService; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.web.app.servlet.AuthenticationHelper; +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.bean.ErrorBean; +import org.alfresco.web.bean.dashboard.DashboardManager; +import org.alfresco.web.bean.dialog.DialogManager; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.bean.wizard.WizardManager; +import org.alfresco.web.config.ClientConfigElement; +import org.apache.commons.logging.Log; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Utilities class + * + * @author gavinc + */ +public class Application +{ + private static final String LOCALE = "locale"; + + public static final String BEAN_CONFIG_SERVICE = "webClientConfigService"; + public static final String BEAN_DATA_DICTIONARY = "dataDictionary"; + public static final String BEAN_IMPORTER_BOOTSTRAP = "spacesBootstrap"; + + public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; + + private static boolean inPortalServer = false; + private static StoreRef repoStoreRef; + private static String rootPath; + private static String companyRootId; + private static String glossaryFolderName; + private static String spaceTemplatesFolderName; + private static String contentTemplatesFolderName; + private static String emailTemplatesFolderName; + private static String rssTemplatesFolderName; + private static String savedSearchesFolderName; + private static String scriptsFolderName; + private static String guestHomeFolderName; + private static String websitesFolderName; + private static String contentFormsFolderName; + + /** + * Private constructor to prevent instantiation of this class + */ + private Application() + { + } + + /** + * Sets whether this application is running inside a portal server + * + * @param inPortal true to indicate the application is running as a portlet + */ + public static void setInPortalServer(boolean inPortal) + { + inPortalServer = inPortal; + } + + /** + * Determines whether the server is running in a portal + * + * @return true if we are running inside a portal server + */ + public static boolean inPortalServer() + { + return inPortalServer; + } + + /** + * Handles errors thrown from servlets + * + * @param servletContext The servlet context + * @param request The HTTP request + * @param response The HTTP response + * @param error The exception + * @param logger The logger + */ + public static void handleServletError(ServletContext servletContext, HttpServletRequest request, + HttpServletResponse response, Throwable error, Log logger, String returnPage) + throws IOException, ServletException + { + // get the error bean from the session and set the error that occurred. + HttpSession session = request.getSession(); + ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME); + if (errorBean == null) + { + errorBean = new ErrorBean(); + session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean); + } + errorBean.setLastError(error); + errorBean.setReturnPage(returnPage); + + // try and find the configured error page + boolean errorShown = false; + String errorPage = getErrorPage(servletContext); + + if (errorPage != null) + { + if (logger.isDebugEnabled()) + logger.debug("An error has occurred, redirecting to error page: " + errorPage); + + if (response.isCommitted() == false) + { + errorShown = true; + response.sendRedirect(request.getContextPath() + errorPage); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Response is already committed, re-throwing error"); + } + } + else + { + if (logger.isDebugEnabled()) + logger.debug("No error page defined, re-throwing error"); + } + + // if we could not show the error page for whatever reason, re-throw the error + if (!errorShown) + { + if (error instanceof IOException) + { + throw (IOException)error; + } + else if (error instanceof ServletException) + { + throw (ServletException)error; + } + else + { + throw new ServletException(error); + } + } + } + + /** + * Retrieves the DialogManager managed bean + * + * @return DialogManager bean + */ + public static DialogManager getDialogManager() + { + return (DialogManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "DialogManager"); + } + + /** + * Retrieves the WizardManager managed bean + * + * @return WizardManager bean + */ + public static WizardManager getWizardManager() + { + return (WizardManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "WizardManager"); + } + + /** + * Retrieves the DashboardManager managed bean + * + * @return DashboardManager bean + */ + public static DashboardManager getDashboardManager() + { + return (DashboardManager)FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), "DashboardManager"); + } + + /** + * Retrieves the configured error page for the application + * + * @param servletContext The servlet context + * @return The configured error page or null if the configuration is missing + */ + public static String getErrorPage(ServletContext servletContext) + { + return getErrorPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); + } + + /** + * Retrieves the configured error page for the application + * + * @param portletContext The portlet context + * @return + */ + public static String getErrorPage(PortletContext portletContext) + { + return getErrorPage((WebApplicationContext)portletContext.getAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); + } + + /** + * Retrieves the configured login page for the application + * + * @param servletContext The servlet context + * @return The configured login page or null if the configuration is missing + */ + public static String getLoginPage(ServletContext servletContext) + { + return getLoginPage(WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)); + } + + /** + * Retrieves the configured login page for the application + * + * @param portletContext The portlet context + * @return + */ + public static String getLoginPage(PortletContext portletContext) + { + return getLoginPage((WebApplicationContext)portletContext.getAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); + } + + /** + * @return Returns the User object representing the currently logged in user + */ + public static User getCurrentUser(HttpSession session) + { + return (User)session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); + } + + /** + * @return Returns the User object representing the currently logged in user + */ + public static User getCurrentUser(FacesContext context) + { + return (User)context.getExternalContext().getSessionMap().get(AuthenticationHelper.AUTHENTICATION_USER); + } + + /** + * @return Returns the repository store URL (retrieved from config service) + */ + public static StoreRef getRepositoryStoreRef(ServletContext context) + { + return getRepositoryStoreRef(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the repository store URL (retrieved from config service) + */ + public static StoreRef getRepositoryStoreRef(FacesContext context) + { + return getRepositoryStoreRef(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns id of the company root + */ + public static String getCompanyRootId() + { + return companyRootId; + } + + /** + * Sets the company root id. This is setup by the ContextListener. + * + * @param id The company root id + */ + public static void setCompanyRootId(String id) + { + companyRootId = id; + } + + /** + * @return Returns the root path for the application + */ + public static String getRootPath(ServletContext context) + { + return getRootPath(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the root path for the application + */ + public static String getRootPath(FacesContext context) + { + return getRootPath(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the glossary folder name + */ + public static String getGlossaryFolderName(ServletContext context) + { + return getGlossaryFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the glossary folder name + */ + public static String getGlossaryFolderName(FacesContext context) + { + return getGlossaryFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Space templates folder name + */ + public static String getSpaceTemplatesFolderName(ServletContext context) + { + return getSpaceTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Space templates folder name + */ + public static String getSpaceTemplatesFolderName(FacesContext context) + { + return getSpaceTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Content templates folder name + */ + public static String getContentTemplatesFolderName(ServletContext context) + { + return getContentTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Content templates folder name + */ + public static String getContentTemplatesFolderName(FacesContext context) + { + return getContentTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Email templates folder name + */ + public static String getEmailTemplatesFolderName(ServletContext context) + { + return getEmailTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the Email templates folder name + */ + public static String getEmailTemplatesFolderName(FacesContext context) + { + return getEmailTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the RSS templates folder name + */ + public static String getRSSTemplatesFolderName(ServletContext context) + { + return getRSSTemplatesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Returns the RSS templates folder name + */ + public static String getRSSTemplatesFolderName(FacesContext context) + { + return getRSSTemplatesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the Saved Searches folder name + */ + public static String getSavedSearchesFolderName(ServletContext context) + { + return getSavedSearchesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the Saved Searches folder name + */ + public static String getSavedSearchesFolderName(FacesContext context) + { + return getSavedSearchesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the JavaScript scripts folder name + */ + public static String getScriptsFolderName(ServletContext context) + { + return getScriptsFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the JavaScript scripts folder name + */ + public static String getScriptsFolderName(FacesContext context) + { + return getScriptsFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the Guest Home folder name + */ + public static String getGuestHomeFolderName(ServletContext context) + { + return getGuestHomeFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return Return the Guest Home folder name + */ + public static String getGuestHomeFolderName(FacesContext context) + { + return getGuestHomeFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return the Websites folder name + */ + public static String getWebsitesFolderName(ServletContext context) + { + return getWebsitesFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return the Websites folder name + */ + public static String getWebsitesFolderName(FacesContext context) + { + return getWebsitesFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return the WCM Content Forms folder name + */ + public static String getContentFormsFolderName(ServletContext context) + { + return getContentFormsFolderName(WebApplicationContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * @return the WCM Content Forms folder name + */ + public static String getContentFormsFolderName(FacesContext context) + { + return getContentFormsFolderName(FacesContextUtils.getRequiredWebApplicationContext(context)); + } + + /** + * Set the language locale for the current user context + * + * @param context FacesContext for current user + * @param code The ISO locale code to set + */ + public static void setLanguage(FacesContext context, String code) + { + Locale locale = parseLocale(code); + + // set locale for JSF framework usage + context.getViewRoot().setLocale(locale); + + // set locale for our framework usage + context.getExternalContext().getSessionMap().put(LOCALE, locale); + + // clear the current message bundle - so it's reloaded with new locale + context.getExternalContext().getSessionMap().remove(MESSAGE_BUNDLE); + } + + /** + * Set the language locale for the current user session + * + * @param session HttpSession for current user + * @param code The ISO locale code to set + */ + public static void setLanguage(HttpSession session, String code) + { + Locale locale = parseLocale(code); + + session.putValue(LOCALE, locale); + session.removeAttribute(MESSAGE_BUNDLE); + } + + /** + * @param code Locale code (java format with underscores) to parse + * @return Locale object or default if unable to parse + */ + private static Locale parseLocale(String code) + { + Locale locale = Locale.getDefault(); + + StringTokenizer t = new StringTokenizer(code, "_"); + int tokens = t.countTokens(); + if (tokens == 1) + { + locale = new Locale(code); + } + else if (tokens == 2) + { + locale = new Locale(t.nextToken(), t.nextToken()); + } + else if (tokens == 3) + { + locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken()); + } + + return locale; + } + + /** + * Return the language Locale for the current user context + * + * @param context FacesContext for the current user + * + * @return Current language Locale set or the VM default if none set + */ + public static Locale getLanguage(FacesContext context) + { + Locale locale = (Locale)context.getExternalContext().getSessionMap().get(LOCALE); + return locale != null ? locale : Locale.getDefault(); + } + + /** + * Return the language Locale for the current user Session. + * + * @param session HttpSession for the current user + * + * @return Current language Locale set or the VM default if none set + */ + public static Locale getLanguage(HttpSession session) + { + Locale locale = (Locale)session.getAttribute(LOCALE); + return locale != null ? locale : Locale.getDefault(); + } + + /** + * Return the language Locale for the current user PortletSession. + * + * @param session PortletSession for the current user + * + * @return Current language Locale set or the VM default if none set + */ + public static Locale getLanguage(PortletSession session) + { + Locale locale = (Locale)session.getAttribute(LOCALE); + return locale != null ? locale : Locale.getDefault(); + } + + /** + * Get the specified I18N message string from the default message bundle for this user + * + * @param context FacesContext + * @param msg Message ID + * + * @return String from message bundle or $$msg$$ if not found + */ + public static String getMessage(FacesContext context, String msg) + { + return getBundle(context).getString(msg); + } + + /** + * Get the specified I18N message string from the default message bundle for this user + * + * @param session HttpSession + * @param msg Message ID + * + * @return String from message bundle or $$msg$$ if not found + */ + public static String getMessage(HttpSession session, String msg) + { + return getBundle(session).getString(msg); + } + + /** + * Get the specified the default message bundle for this user + * + * @param session HttpSession + * + * @return ResourceBundle for this user + */ + public static ResourceBundle getBundle(HttpSession session) + { + ResourceBundle bundle = (ResourceBundle)session.getAttribute(MESSAGE_BUNDLE); + if (bundle == null) + { + // get Locale from language selected by each user on login + Locale locale = (Locale)session.getAttribute(LOCALE); + if (locale == null) + { + locale = Locale.getDefault(); + } + bundle = ResourceBundleWrapper.getResourceBundle(MESSAGE_BUNDLE, locale); + + session.setAttribute(MESSAGE_BUNDLE, bundle); + } + + return bundle; + } + + /** + * Get the specified the default message bundle for this user + * + * @param context FacesContext + * + * @return ResourceBundle for this user + */ + public static ResourceBundle getBundle(FacesContext context) + { + // get the resource bundle for the current locale + // we store the bundle in the users session + // this makes it easy to add a locale per user support later + Map session = context.getExternalContext().getSessionMap(); + ResourceBundle bundle = (ResourceBundle)session.get(MESSAGE_BUNDLE); + if (bundle == null) + { + // get Locale from language selected by each user on login + Locale locale = (Locale)session.get(LOCALE); + if (locale == null) + { + locale = Locale.getDefault(); + } + bundle = ResourceBundleWrapper.getResourceBundle(MESSAGE_BUNDLE, locale); + + session.put(MESSAGE_BUNDLE, bundle); + } + + return bundle; + } + + /** + * Helper to get the ConfigService instance + * + * @param context FacesContext + * + * @return ConfigService + */ + public static ConfigService getConfigService(FacesContext context) + { + return (ConfigService)FacesContextUtils.getRequiredWebApplicationContext(context).getBean( + Application.BEAN_CONFIG_SERVICE); + } + + /** + * Helper to get the ConfigService instance + * + * @param context ServletContext + * + * @return ConfigService + */ + public static ConfigService getConfigService(ServletContext context) + { + return (ConfigService)WebApplicationContextUtils.getRequiredWebApplicationContext(context).getBean( + Application.BEAN_CONFIG_SERVICE); + } + + /** + * Helper to get the client config element from the config service + * + * @param context FacesContext + * @return The ClientConfigElement + */ + public static ClientConfigElement getClientConfig(FacesContext context) + { + return (ClientConfigElement)getConfigService(context).getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + } + + /** + * Returns the repository store URL + * + * @param context The spring context + * @return The repository store URL to use + */ + private static StoreRef getRepositoryStoreRef(WebApplicationContext context) + { + if (repoStoreRef == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + repoStoreRef = bootstrap.getStoreRef(); + } + + return repoStoreRef; + } + + /** + * Returns the root path for the application + * + * @param context The spring context + * @return The application root path + */ + private static String getRootPath(WebApplicationContext context) + { + if (rootPath == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + rootPath = configuration.getProperty("spaces.company_home.childname"); + } + + return rootPath; + } + + /** + * Returns the glossary folder name + * + * @param context The spring context + * @return The glossary folder name + */ + private static String getGlossaryFolderName(WebApplicationContext context) + { + if (glossaryFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + glossaryFolderName = configuration.getProperty("spaces.dictionary.childname"); + } + + return glossaryFolderName; + } + + /** + * Returns the Space Templates folder name + * + * @param context The spring context + * @return The templates folder name + */ + private static String getSpaceTemplatesFolderName(WebApplicationContext context) + { + if (spaceTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + spaceTemplatesFolderName = configuration.getProperty("spaces.templates.childname"); + } + + return spaceTemplatesFolderName; + } + + /** + * Returns the Content Templates folder name + * + * @param context The spring context + * @return The templates folder name + */ + private static String getContentTemplatesFolderName(WebApplicationContext context) + { + if (contentTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + contentTemplatesFolderName = configuration.getProperty("spaces.templates.content.childname"); + } + + return contentTemplatesFolderName; + } + + /** + * Returns the Email Templates folder name + * + * @param context The spring context + * @return The email folder name + */ + private static String getEmailTemplatesFolderName(WebApplicationContext context) + { + if (emailTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + emailTemplatesFolderName = configuration.getProperty("spaces.templates.email.childname"); + } + + return emailTemplatesFolderName; + } + + /** + * Returns the RSS Templates folder name + * + * @param context The spring context + * @return The RSS folder name + */ + private static String getRSSTemplatesFolderName(WebApplicationContext context) + { + if (rssTemplatesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + rssTemplatesFolderName = configuration.getProperty("spaces.templates.rss.childname"); + } + + return rssTemplatesFolderName; + } + + /** + * Returns the Saved Searches folder name + * + * @param context The spring context + * @return The saved searches folder name + */ + private static String getSavedSearchesFolderName(WebApplicationContext context) + { + /* + * This lookup is duplicated in a patch. If this logic changes, investigate the changes + * required for the patch(es). + */ + + if (savedSearchesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + savedSearchesFolderName = configuration.getProperty("spaces.savedsearches.childname"); + } + + return savedSearchesFolderName; + } + + /** + * Returns the JavaScript scripts folder name + * + * @param context The spring context + * @return The scripts folder name + */ + private static String getScriptsFolderName(WebApplicationContext context) + { + if (scriptsFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + scriptsFolderName = configuration.getProperty("spaces.scripts.childname"); + } + + return scriptsFolderName; + } + + /** + * Returns the Guest Home folder name name + * + * @param context The spring context + * @return The Guest Home folder name + */ + private static String getGuestHomeFolderName(WebApplicationContext context) + { + if (guestHomeFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + guestHomeFolderName = configuration.getProperty("spaces.guest_home.childname"); + } + + return guestHomeFolderName; + } + + /** + * Returns the Websites folder name + * + * @param context The Spring context + * @return The Websites folder name + */ + private static String getWebsitesFolderName(WebApplicationContext context) + { + if (websitesFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + websitesFolderName = configuration.getProperty("spaces.wcm.childname"); + } + + return websitesFolderName; + } + + /** + * Returns the WCM Content Forms folder name + * + * @param context The Spring context + * @return The WCM Content Forms folder name + */ + private static String getContentFormsFolderName(WebApplicationContext context) + { + if (contentFormsFolderName == null) + { + ImporterBootstrap bootstrap = (ImporterBootstrap)context.getBean(BEAN_IMPORTER_BOOTSTRAP); + Properties configuration = bootstrap.getConfiguration(); + contentFormsFolderName = configuration.getProperty("spaces.content_forms.childname"); + } + + return contentFormsFolderName; + } + + /** + * Retrieves the configured error page for the application + * + * @param context The Spring context + * @return The configured error page or null if the configuration is missing + */ + private static String getErrorPage(WebApplicationContext context) + { + String errorPage = null; + + ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); + ClientConfigElement clientConfig = (ClientConfigElement)svc.getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + + if (clientConfig != null) + { + errorPage = clientConfig.getErrorPage(); + } + + return errorPage; + } + + /** + * Retrieves the configured login page for the application + * + * @param context The Spring contexr + * @return The configured login page or null if the configuration is missing + */ + private static String getLoginPage(WebApplicationContext context) + { + String loginPage = null; + + ConfigService svc = (ConfigService)context.getBean(BEAN_CONFIG_SERVICE); + ClientConfigElement clientConfig = (ClientConfigElement)svc.getGlobalConfig(). + getConfigElement(ClientConfigElement.CONFIG_ELEMENT_ID); + + if (clientConfig != null) + { + loginPage = clientConfig.getLoginPage(); + } + + return loginPage; + } +} diff --git a/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java b/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java new file mode 100644 index 0000000000..9b9a7f69f1 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/AdminAuthenticationFilter.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.web.bean.repository.User; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This servlet filter is used to restrict direct URL access to administration + * resource in the web client, for example the admin and jBPM consoles. + * + * @author gavinc + */ +public class AdminAuthenticationFilter implements Filter +{ + private static final Log logger = LogFactory.getLog(AdminAuthenticationFilter.class); + + /** + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest httpRequest = (HttpServletRequest)req; + HttpServletResponse httpResponse = (HttpServletResponse)res; + + // The fact that this filter is being called means a request for a protected + // resource has taken place, check that the current user is in fact an + // administrator. + + if (logger.isDebugEnabled()) + logger.debug("Authorising request for protected resource: " + httpRequest.getRequestURI()); + + // there should be a user at this point so retrieve it + User user = AuthenticationHelper.getUser(httpRequest, httpResponse); + + // if the user is present check to see whether it is an admin user + boolean isAdmin = (user != null && user.isAdmin()); + + if (isAdmin) + { + if (logger.isDebugEnabled()) + logger.debug("Current user has admin authority, allowing access."); + + // continue filter chaining if current user is admin user + chain.doFilter(req, res); + } + else + { + // return the 401 Forbidden error as the current user is not an administrator + // if the response has already been committed there's nothing we can do but + // print out a warning + if (httpResponse.isCommitted() == false) + { + if (logger.isDebugEnabled()) + logger.debug("Current user does not have admin authority, returning 401 Forbidden error..."); + + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else + { + if (logger.isWarnEnabled()) + logger.warn("Access denied to '" + httpRequest.getRequestURI() + + "'. The response has already been committed so a 401 Forbidden error could not be sent!"); + } + } + } + + /** + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + public void init(FilterConfig config) throws ServletException + { + // nothing to do + } + + /** + * @see javax.servlet.Filter#destroy() + */ + public void destroy() + { + // nothing to do + } +} diff --git a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java index 7238435bf0..611e0dd28d 100644 --- a/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java +++ b/source/java/org/alfresco/web/app/servlet/AuthenticationHelper.java @@ -100,39 +100,15 @@ public final class AuthenticationHelper { HttpSession session = httpRequest.getSession(); - // examine the appropriate session for our User object - User user = null; + // retrieve the User object + User user = getUser(httpRequest, httpResponse); + + // get the login bean if we're not in the portal LoginBean loginBean = null; if (Application.inPortalServer() == false) { - user = (User)session.getAttribute(AUTHENTICATION_USER); loginBean = (LoginBean)session.getAttribute(LOGIN_BEAN); } - else - { - // naff solution as we need to enumerate all session keys until we find the one that - // should match our User objects - this is weak but we don't know how the underlying - // Portal vendor has decided to encode the objects in the session - if (portalUserKeyName.get() == null) - { - String userKeyPostfix = "?" + AUTHENTICATION_USER; - Enumeration enumNames = session.getAttributeNames(); - while (enumNames.hasMoreElements()) - { - String name = (String)enumNames.nextElement(); - if (name.endsWith(userKeyPostfix)) - { - // cache the key value once found! - portalUserKeyName.set(name); - break; - } - } - } - if (portalUserKeyName.get() != null) - { - user = (User)session.getAttribute(portalUserKeyName.get()); - } - } // setup the authentication context WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(context); @@ -388,6 +364,52 @@ public final class AuthenticationHelper return AuthenticationStatus.Failure; } + /** + * Attempts to retrieve the User object stored in the current session. + * + * @param httpRequest The HTTP request + * @param httpResponse The HTTP response + * @return The User object representing the current user or null if it could not be found + */ + public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse) + { + HttpSession session = httpRequest.getSession(); + User user = null; + + // examine the appropriate session to try and find the User object + if (Application.inPortalServer() == false) + { + user = (User)session.getAttribute(AUTHENTICATION_USER); + } + else + { + // naff solution as we need to enumerate all session keys until we find the one that + // should match our User objects - this is weak but we don't know how the underlying + // Portal vendor has decided to encode the objects in the session + if (portalUserKeyName.get() == null) + { + String userKeyPostfix = "?" + AUTHENTICATION_USER; + Enumeration enumNames = session.getAttributeNames(); + while (enumNames.hasMoreElements()) + { + String name = (String)enumNames.nextElement(); + if (name.endsWith(userKeyPostfix)) + { + // cache the key value once found! + portalUserKeyName.set(name); + break; + } + } + } + if (portalUserKeyName.get() != null) + { + user = (User)session.getAttribute(portalUserKeyName.get()); + } + } + + return user; + } + /** * Setup the Alfresco auth cookie value. * diff --git a/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java b/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java new file mode 100644 index 0000000000..11a965866f --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/BaseDownloadContentServlet.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.SocketException; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.Date; +import java.util.StringTokenizer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.ui.common.Utils; +import org.apache.commons.logging.Log; + +/** + * Base class for the download content servlets. Provides common + * processing for the request. + * + * @see org.alfresco.web.app.servlet.DownloadContentServlet + * @see org.alfresco.web.app.servlet.GuestDownloadContentServlet + * + * @author Kevin Roast + * @author gavinc + */ +public abstract class BaseDownloadContentServlet extends BaseServlet +{ + private static final long serialVersionUID = -4558907921887235966L; + + protected static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; + + protected static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing"; + + protected static final String ARG_PROPERTY = "property"; + protected static final String ARG_ATTACH = "attach"; + protected static final String ARG_PATH = "path"; + + /** + * Gets the logger to use for this request. + *

+ * This will show all debug entries from this class as though they + * came from the subclass. + * + * @return The logger + */ + protected abstract Log getLogger(); + + /** + * Processes the download request using the current context i.e. no + * authentication checks are made, it is presumed they have already + * been done. + * + * @param req The HTTP request + * @param res The HTTP response + * @param redirectToLogin Flag to determine whether to redirect to the login + * page if the user does not have the correct permissions + */ + protected void processDownloadRequest(HttpServletRequest req, HttpServletResponse res, + boolean redirectToLogin) + throws ServletException, IOException + { + Log logger = getLogger(); + String uri = req.getRequestURI(); + + if (logger.isDebugEnabled()) + { + String queryString = req.getQueryString(); + logger.debug("Processing URL: " + uri + + ((queryString != null && queryString.length() > 0) ? ("?" + queryString) : "")); + } + + // TODO: add compression here? + // see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example + // only really needed if we don't use the built in compression of the servlet container + uri = uri.substring(req.getContextPath().length()); + StringTokenizer t = new StringTokenizer(uri, "/"); + int tokenCount = t.countTokens(); + + t.nextToken(); // skip servlet name + + // attachment mode (either 'attach' or 'direct') + String attachToken = t.nextToken(); + boolean attachment = attachToken.equals(ARG_ATTACH); + + // get or calculate the noderef and filename to download as + NodeRef nodeRef; + String filename; + + // do we have a path parameter instead of a NodeRef? + String path = req.getParameter(ARG_PATH); + if (path != null && path.length() != 0) + { + // process the name based path to resolve the NodeRef and the Filename element + PathRefInfo pathInfo = resolveNamePath(getServletContext(), path); + + nodeRef = pathInfo.NodeRef; + filename = pathInfo.Filename; + } + else + { + // a NodeRef must have been specified if no path has been found + if (tokenCount < 6) + { + throw new IllegalArgumentException("Download URL did not contain all required args: " + uri); + } + + // assume 'workspace' or other NodeRef based protocol for remaining URL elements + StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken()); + String id = t.nextToken(); + // build noderef from the appropriate URL elements + nodeRef = new NodeRef(storeRef, id); + + // filename is last remaining token + filename = t.nextToken(); + } + + // get qualified of the property to get content from - default to ContentModel.PROP_CONTENT + QName propertyQName = ContentModel.PROP_CONTENT; + String property = req.getParameter(ARG_PROPERTY); + if (property != null && property.length() != 0) + { + propertyQName = QName.createQName(property); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Found NodeRef: " + nodeRef.toString()); + logger.debug("Will use filename: " + filename); + logger.debug("For property: " + propertyQName); + logger.debug("With attachment mode: " + attachment); + } + + // get the services we need to retrieve the content + ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext()); + NodeService nodeService = serviceRegistry.getNodeService(); + ContentService contentService = serviceRegistry.getContentService(); + PermissionService permissionService = serviceRegistry.getPermissionService(); + + try + { + // check that the user has at least READ_CONTENT access - else redirect to the login page + if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) + { + if (logger.isDebugEnabled()) + logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString()); + + if (redirectToLogin) + { + if (logger.isDebugEnabled()) + logger.debug("Redirecting to login page..."); + + redirectToLoginPage(req, res, getServletContext()); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Returning 403 Forbidden error..."); + + res.sendError(HttpServletResponse.SC_FORBIDDEN); + } + return; + } + + // check If-Modified-Since header and set Last-Modified header as appropriate + Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + long modifiedSince = req.getDateHeader("If-Modified-Since"); + if (modifiedSince > 0L) + { + // round the date to the ignore millisecond value which is not supplied by header + long modDate = (modified.getTime() / 1000L) * 1000L; + if (modDate <= modifiedSince) + { + res.setStatus(304); + return; + } + } + res.setDateHeader("Last-Modified", modified.getTime()); + + if (attachment == true) + { + // set header based on filename - will force a Save As from the browse if it doesn't recognise it + // this is better than the default response of the browser trying to display the contents + res.setHeader("Content-Disposition", "attachment"); + } + + // get the content reader + ContentReader reader = contentService.getReader(nodeRef, propertyQName); + // ensure that it is safe to use + reader = FileContentReader.getSafeContentReader( + reader, + Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING), + nodeRef, reader); + + String mimetype = reader.getMimetype(); + // fall back if unable to resolve mimetype property + if (mimetype == null || mimetype.length() == 0) + { + MimetypeService mimetypeMap = serviceRegistry.getMimetypeService(); + mimetype = MIMETYPE_OCTET_STREAM; + int extIndex = filename.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = filename.substring(extIndex + 1); + String mt = mimetypeMap.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + } + // set mimetype for the content and the character encoding for the stream + res.setContentType(mimetype); + res.setCharacterEncoding(reader.getEncoding()); + + // get the content and stream directly to the response output stream + // assuming the repo is capable of streaming in chunks, this should allow large files + // to be streamed directly to the browser response stream. + try + { + reader.getContent( res.getOutputStream() ); + } + catch (SocketException e) + { + if (e.getMessage().contains("ClientAbortException")) + { + // the client cut the connection - our mission was accomplished apart from a little error message + logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader); + } + else + { + throw e; + } + } + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err); + } + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * + * @param pattern The pattern to use for the URL + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + protected final static String generateUrl(String pattern, NodeRef ref, String name) + { + String url = null; + + try + { + url = MessageFormat.format(pattern, new Object[] { + ref.getStoreRef().getProtocol(), + ref.getStoreRef().getIdentifier(), + ref.getId(), + Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } ); + } + catch (UnsupportedEncodingException uee) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee); + } + + return url; + } +} diff --git a/source/java/org/alfresco/web/app/servlet/CommandServlet.java b/source/java/org/alfresco/web/app/servlet/CommandServlet.java index 0beb509ddc..69923e6026 100644 --- a/source/java/org/alfresco/web/app/servlet/CommandServlet.java +++ b/source/java/org/alfresco/web/app/servlet/CommandServlet.java @@ -73,7 +73,7 @@ public class CommandServlet extends BaseServlet /** * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ - protected void doGet(HttpServletRequest req, HttpServletResponse res) + protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String uri = req.getRequestURI(); diff --git a/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java b/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java index 722f01dfb8..afcd7b9bf4 100644 --- a/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java +++ b/source/java/org/alfresco/web/app/servlet/DownloadContentServlet.java @@ -17,33 +17,12 @@ package org.alfresco.web.app.servlet; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.SocketException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.text.MessageFormat; -import java.util.Date; -import java.util.StringTokenizer; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.web.app.Application; -import org.alfresco.web.ui.common.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -72,26 +51,30 @@ import org.apache.commons.logging.LogFactory; * Like most Alfresco servlets, the URL may be followed by a valid 'ticket' argument for authentication: * ?ticket=1234567890 *

- * And/or also followed by the "?guest=true" argument to force guest access login for the URL. + * And/or also followed by the "?guest=true" argument to force guest access login for the URL. If the + * guest=true parameter is used the current session will be logged out and the guest user logged in. + * Therefore upon completion of this request the current user will be "guest". + *

+ * If the user attempting the request is not authorised to access the requested node the login page + * will be redirected to. * * @author Kevin Roast + * @author gavinc */ -public class DownloadContentServlet extends BaseServlet +public class DownloadContentServlet extends BaseDownloadContentServlet { - private static final long serialVersionUID = -4558907921887235966L; - + private static final long serialVersionUID = -576405943603122206L; + private static Log logger = LogFactory.getLog(DownloadContentServlet.class); private static final String DOWNLOAD_URL = "/download/attach/{0}/{1}/{2}/{3}"; private static final String BROWSER_URL = "/download/direct/{0}/{1}/{2}/{3}"; - private static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; - - private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing"; - - private static final String ARG_PROPERTY = "property"; - private static final String ARG_ATTACH = "attach"; - private static final String ARG_PATH = "path"; + @Override + protected Log getLogger() + { + return logger; + } /** * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) @@ -99,10 +82,12 @@ public class DownloadContentServlet extends BaseServlet protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - String uri = req.getRequestURI(); - if (logger.isDebugEnabled()) - logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : "")); + { + String queryString = req.getQueryString(); + logger.debug("Authenticating request to URL: " + req.getRequestURI() + + ((queryString != null && queryString.length() > 0) ? ("?" + queryString) : "")); + } AuthenticationStatus status = servletAuthenticate(req, res); if (status == AuthenticationStatus.Failure) @@ -110,159 +95,7 @@ public class DownloadContentServlet extends BaseServlet return; } - // TODO: add compression here? - // see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example - // only really needed if we don't use the built in compression of the servlet container - uri = uri.substring(req.getContextPath().length()); - StringTokenizer t = new StringTokenizer(uri, "/"); - int tokenCount = t.countTokens(); - - t.nextToken(); // skip servlet name - - // attachment mode (either 'attach' or 'direct') - String attachToken = t.nextToken(); - boolean attachment = attachToken.equals(ARG_ATTACH); - - // get or calculate the noderef and filename to download as - NodeRef nodeRef; - String filename; - - // do we have a path parameter instead of a NodeRef? - String path = req.getParameter(ARG_PATH); - if (path != null && path.length() != 0) - { - // process the name based path to resolve the NodeRef and the Filename element - PathRefInfo pathInfo = resolveNamePath(getServletContext(), path); - - nodeRef = pathInfo.NodeRef; - filename = pathInfo.Filename; - } - else - { - // a NodeRef must have been specified if no path has been found - if (tokenCount < 6) - { - throw new IllegalArgumentException("Download URL did not contain all required args: " + uri); - } - - // assume 'workspace' or other NodeRef based protocol for remaining URL elements - StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken()); - String id = URLDecoder.decode(t.nextToken(), "UTF-8"); - // build noderef from the appropriate URL elements - nodeRef = new NodeRef(storeRef, id); - - // filename is last remaining token - filename = t.nextToken(); - } - - // get qualified of the property to get content from - default to ContentModel.PROP_CONTENT - QName propertyQName = ContentModel.PROP_CONTENT; - String property = req.getParameter(ARG_PROPERTY); - if (property != null && property.length() != 0) - { - propertyQName = QName.createQName(property); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Found NodeRef: " + nodeRef.toString()); - logger.debug("Will use filename: " + filename); - logger.debug("For property: " + propertyQName); - logger.debug("With attachment mode: " + attachment); - } - - // get the services we need to retrieve the content - ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext()); - NodeService nodeService = serviceRegistry.getNodeService(); - ContentService contentService = serviceRegistry.getContentService(); - PermissionService permissionService = serviceRegistry.getPermissionService(); - - try - { - // check that the user has at least READ_CONTENT access - else redirect to the login page - if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) - { - if (logger.isDebugEnabled()) - logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString()); - redirectToLoginPage(req, res, getServletContext()); - return; - } - - // check If-Modified-Since header and set Last-Modified header as appropriate - Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); - long modifiedSince = req.getDateHeader("If-Modified-Since"); - if (modifiedSince > 0L) - { - // round the date to the ignore millisecond value which is not supplied by header - long modDate = (modified.getTime() / 1000L) * 1000L; - if (modDate <= modifiedSince) - { - res.setStatus(304); - return; - } - } - res.setDateHeader("Last-Modified", modified.getTime()); - - if (attachment == true) - { - // set header based on filename - will force a Save As from the browse if it doesn't recognise it - // this is better than the default response of the browser trying to display the contents - res.setHeader("Content-Disposition", "attachment"); - } - - // get the content reader - ContentReader reader = contentService.getReader(nodeRef, propertyQName); - // ensure that it is safe to use - reader = FileContentReader.getSafeContentReader( - reader, - Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING), - nodeRef, reader); - - String mimetype = reader.getMimetype(); - // fall back if unable to resolve mimetype property - if (mimetype == null || mimetype.length() == 0) - { - MimetypeService mimetypeMap = serviceRegistry.getMimetypeService(); - mimetype = MIMETYPE_OCTET_STREAM; - int extIndex = filename.lastIndexOf('.'); - if (extIndex != -1) - { - String ext = filename.substring(extIndex + 1); - String mt = mimetypeMap.getMimetypesByExtension().get(ext); - if (mt != null) - { - mimetype = mt; - } - } - } - // set mimetype for the content and the character encoding for the stream - res.setContentType(mimetype); - res.setCharacterEncoding(reader.getEncoding()); - - // get the content and stream directly to the response output stream - // assuming the repo is capable of streaming in chunks, this should allow large files - // to be streamed directly to the browser response stream. - try - { - reader.getContent( res.getOutputStream() ); - } - catch (SocketException e) - { - if (e.getMessage().contains("ClientAbortException")) - { - // the client cut the connection - our mission was accomplished apart from a little error message - logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader); - } - else - { - throw e; - } - } - } - catch (Throwable err) - { - throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err); - } + processDownloadRequest(req, res, true); } /** @@ -277,22 +110,7 @@ public class DownloadContentServlet extends BaseServlet */ public final static String generateDownloadURL(NodeRef ref, String name) { - String url = null; - - try - { - url = MessageFormat.format(DOWNLOAD_URL, new Object[] { - ref.getStoreRef().getProtocol(), - ref.getStoreRef().getIdentifier(), - ref.getId(), - Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } ); - } - catch (UnsupportedEncodingException uee) - { - throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee); - } - - return url; + return generateUrl(DOWNLOAD_URL, ref, name); } /** @@ -307,21 +125,6 @@ public class DownloadContentServlet extends BaseServlet */ public final static String generateBrowserURL(NodeRef ref, String name) { - String url = null; - - try - { - url = MessageFormat.format(BROWSER_URL, new Object[] { - ref.getStoreRef().getProtocol(), - ref.getStoreRef().getIdentifier(), - ref.getId(), - Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } ); - } - catch (UnsupportedEncodingException uee) - { - throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee); - } - - return url; + return generateUrl(BROWSER_URL, ref, name); } } diff --git a/source/java/org/alfresco/web/app/servlet/FacesHelper.java b/source/java/org/alfresco/web/app/servlet/FacesHelper.java index 7b8a008a0b..701ac2b0f2 100644 --- a/source/java/org/alfresco/web/app/servlet/FacesHelper.java +++ b/source/java/org/alfresco/web/app/servlet/FacesHelper.java @@ -145,12 +145,31 @@ public final class FacesHelper else { // make sure we do not have illegal characters in the id + id = makeLegalId(id); + } + + component.setId(id); + } + + /** + * Makes the given id a legal JSF component id by replacing illegal + * characters with underscores. + * + * @param id The id to make legal + * @return The legalised id + */ + public static String makeLegalId(String id) + { + if (id != null) + { + // replace illegal ID characters with an underscore id = id.replace(':', '_'); + id = id.replace(' ', '_'); // TODO: check all other illegal characters - only allowed dash and underscore } - component.setId(id); + return id; } /** diff --git a/source/java/org/alfresco/web/app/servlet/GuestDownloadContentServlet.java b/source/java/org/alfresco/web/app/servlet/GuestDownloadContentServlet.java new file mode 100644 index 0000000000..5a61b3a868 --- /dev/null +++ b/source/java/org/alfresco/web/app/servlet/GuestDownloadContentServlet.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.app.servlet; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Servlet responsible for streaming node content from the repo directly to the response stream. + * The appropriate mimetype is calculated based on filename extension. + *

+ * The URL to the servlet should be generated thus: + *

/alfresco/guestDownload/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf
+ * or + *
/alfresco/guestDownload/direct/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf
+ * or + *
/alfresco/guestDownload/[direct|attach]?path=/Company%20Home/MyFolder/myfile.pdf
+ * The protocol, followed by either the store and Id (NodeRef) or instead specify a name based + * encoded Path to the content, note that the filename element is used for mimetype lookup and + * as the returning filename for the response stream. + *

+ * The 'attach' or 'direct' element is used to indicate whether to display the stream directly + * in the browser or download it as a file attachment. + *

+ * By default, the download assumes that the content is on the + * {@link org.alfresco.model.ContentModel#PROP_CONTENT content property}.
+ * To retrieve the content of a specific model property, use a 'property' arg, providing the workspace, + * node ID AND the qualified name of the property. + *

+ * This servlet only accesses content available to the guest user. If the guest user does not + * have access to the requested a 401 Forbidden response is returned to the caller. + *

+ * This servlet does not effect the current session, therefore if guest access is required to a + * resource this servlet can be used without logging out the current user. + * + * @author gavinc + */ +public class GuestDownloadContentServlet extends BaseDownloadContentServlet +{ + private static final long serialVersionUID = -5258137503339817457L; + + private static Log logger = LogFactory.getLog(GuestDownloadContentServlet.class); + + private static final String DOWNLOAD_URL = "/guestDownload/attach/{0}/{1}/{2}/{3}"; + private static final String BROWSER_URL = "/guestDownload/direct/{0}/{1}/{2}/{3}"; + + @Override + protected Log getLogger() + { + return logger; + } + + /** + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + if (logger.isDebugEnabled()) + { + String queryString = req.getQueryString(); + logger.debug("Setting up guest access to URL: " + req.getRequestURI() + + ((queryString != null && queryString.length() > 0) ? ("?" + queryString) : "")); + } + + DownloadContentWork dcw = new DownloadContentWork(req, res); + AuthenticationUtil.runAs(dcw, PermissionService.GUEST_AUTHORITY); + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied as an HTTP1.1 attachment to the response. This generally means + * a browser should prompt the user to save the content to specified location. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + public final static String generateDownloadURL(NodeRef ref, String name) + { + return generateUrl(DOWNLOAD_URL, ref, name); + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied directly in the reponse. This generally means a browser will + * attempt to open the content directly if possible, else it will prompt to save the file. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name to return in the URL (cannot be null) + * + * @return URL to download the content from the specified node + */ + public final static String generateBrowserURL(NodeRef ref, String name) + { + return generateUrl(BROWSER_URL, ref, name); + } + + /** + * Class to wrap the call to processDownloadRequest. + * + * @author gavinc + */ + public class DownloadContentWork implements RunAsWork + { + private HttpServletRequest req = null; + private HttpServletResponse res = null; + + public DownloadContentWork(HttpServletRequest req, HttpServletResponse res) + { + this.req = req; + this.res = res; + } + + public Object doWork() throws Exception + { + processDownloadRequest(this.req, this.res, false); + + return null; + } + } +} diff --git a/source/java/org/alfresco/web/app/servlet/JBPMDeployProcessServlet.java b/source/java/org/alfresco/web/app/servlet/JBPMDeployProcessServlet.java index 937af24ad0..09369287f9 100644 --- a/source/java/org/alfresco/web/app/servlet/JBPMDeployProcessServlet.java +++ b/source/java/org/alfresco/web/app/servlet/JBPMDeployProcessServlet.java @@ -26,10 +26,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.workflow.WorkflowComponent; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; +import org.alfresco.service.cmr.workflow.WorkflowService; import org.apache.commons.fileupload.DiskFileUpload; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUpload; @@ -117,10 +118,9 @@ public class JBPMDeployProcessServlet extends HttpServlet */ private WorkflowDefinition deployArchive(InputStream deploymentArchive) { - // NOTE: retrieve jbpm engine directly as this servlet only serves JBPM process designer deployments WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - WorkflowComponent jbpmEngine = (WorkflowComponent)wc.getBean("jbpm_engine"); - WorkflowDeployment deployment = jbpmEngine.deployDefinition(deploymentArchive, MimetypeMap.MIMETYPE_ZIP); + WorkflowService workflowService = (WorkflowService)wc.getBean(ServiceRegistry.WORKFLOW_SERVICE.getLocalName()); + WorkflowDeployment deployment = workflowService.deployDefinition("jbpm", deploymentArchive, MimetypeMap.MIMETYPE_ZIP); return deployment.definition; } diff --git a/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java b/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java index 99fdce8af5..a728e7bc93 100644 --- a/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java +++ b/source/java/org/alfresco/web/app/servlet/TemplateContentServlet.java @@ -86,8 +86,6 @@ public class TemplateContentServlet extends BaseServlet private static final String DEFAULT_URL = "/template/{0}/{1}/{2}"; private static final String TEMPLATE_URL = "/template/{0}/{1}/{2}/{3}/{4}/{5}"; - private static final String MSG_ERROR_CONTENT_MISSING = "error_content_missing"; - private static final String ARG_MIMETYPE = "mimetype"; private static final String ARG_TEMPLATE_PATH = "templatePath"; private static final String ARG_CONTEXT_PATH = "contextPath"; @@ -95,13 +93,17 @@ public class TemplateContentServlet extends BaseServlet /** * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ - protected void doGet(HttpServletRequest req, HttpServletResponse res) + protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String uri = req.getRequestURI(); if (logger.isDebugEnabled()) - logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : "")); + { + String queryString = req.getQueryString(); + logger.debug("Processing URL: " + uri + + ((queryString != null && queryString.length() > 0) ? ("?" + queryString) : "")); + } AuthenticationStatus status = servletAuthenticate(req, res); if (status == AuthenticationStatus.Failure) @@ -258,6 +260,7 @@ public class TemplateContentServlet extends BaseServlet * * @return an object model ready for executing template against */ + @SuppressWarnings("unchecked") private Object getModel(ServiceRegistry services, HttpServletRequest req, NodeRef templateRef, NodeRef nodeRef) { // build FreeMarker default model and merge diff --git a/source/java/org/alfresco/web/app/servlet/ajax/AjaxCommand.java b/source/java/org/alfresco/web/app/servlet/ajax/AjaxCommand.java index 17eba223be..c61a623e8c 100644 --- a/source/java/org/alfresco/web/app/servlet/ajax/AjaxCommand.java +++ b/source/java/org/alfresco/web/app/servlet/ajax/AjaxCommand.java @@ -22,7 +22,7 @@ public interface AjaxCommand * expression. Parameters required to call the method can be retrieved * from the request. * - * Currently the content type of the response will always be text/html, in the + * Currently the content type of the response will always be text/xml, in the * future sublcasses may provide a mechanism to allow the content type to be set * dynamically. * diff --git a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java index 1dee5a8adc..9810c33f37 100644 --- a/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java +++ b/source/java/org/alfresco/web/app/servlet/ajax/InvokeCommand.java @@ -61,23 +61,24 @@ public class InvokeCommand extends BaseAjaxCommand // NOTE: it doesn't seem to matter what the content type of the response is (at least with Dojo), // it determines it's behaviour from the mimetype specified in the AJAX call on the client, - // therefore, for now we will always return a content type of text/html. + // therefore, for now we will always return a content type of text/xml. // In the future we may use annotations on the method to be called to specify what content // type should be used for the response. - final OutputStream os = response.getOutputStream(); - final UIViewRoot viewRoot = facesContext.getViewRoot(); - final RenderKitFactory renderFactory = (RenderKitFactory)FactoryFinder. + + OutputStream os = response.getOutputStream(); + UIViewRoot viewRoot = facesContext.getViewRoot(); + RenderKitFactory renderFactory = (RenderKitFactory)FactoryFinder. getFactory(FactoryFinder.RENDER_KIT_FACTORY); - final RenderKit renderKit = renderFactory.getRenderKit(facesContext, + RenderKit renderKit = renderFactory.getRenderKit(facesContext, viewRoot.getRenderKitId()); - final ResponseWriter writer = renderKit.createResponseWriter( + ResponseWriter writer = renderKit.createResponseWriter( new OutputStreamWriter(os), MimetypeMap.MIMETYPE_XML, "UTF-8"); facesContext.setResponseWriter(writer); // must be text/xml otherwise IE doesn't parse the response properly into responseXML response.setContentType(MimetypeMap.MIMETYPE_XML); // create the JSF binding expression - final String bindingExpr = makeBindingExpression(expression); + String bindingExpr = makeBindingExpression(expression); if (logger.isDebugEnabled()) logger.debug("Invoking method represented by " + bindingExpr); @@ -86,8 +87,8 @@ public class InvokeCommand extends BaseAjaxCommand try { // create the method binding from the expression - final MethodBinding binding = - facesContext.getApplication().createMethodBinding(bindingExpr, new Class[] {}); + MethodBinding binding = facesContext.getApplication().createMethodBinding( + bindingExpr, new Class[] {}); if (binding != null) { @@ -106,13 +107,13 @@ public class InvokeCommand extends BaseAjaxCommand { // rollback the transaction try { if (tx != null) { tx.rollback(); } } catch (Exception ex) { } - if (err instanceof EvaluationException) - { + if (err instanceof EvaluationException) + { final Throwable cause = ((EvaluationException)err).getCause(); - if (cause != null) - err = cause; - } - logger.error(err); + if (cause != null) + err = cause; + } + logger.error(err); throw new AlfrescoRuntimeException("Failed to execute method " + expression + ": " + err.getMessage(), err); diff --git a/source/java/org/alfresco/web/bean/AdvancedSearchBean.java b/source/java/org/alfresco/web/bean/AdvancedSearchBean.java index 9ee2208ae1..7f47ed15a6 100644 --- a/source/java/org/alfresco/web/bean/AdvancedSearchBean.java +++ b/source/java/org/alfresco/web/bean/AdvancedSearchBean.java @@ -848,7 +848,7 @@ public class AdvancedSearchBean search.addFixedValueQuery(QName.createQName(qname), strVal); } } - else + else if (value != null) { // by default use toString() value - this is for text fields and unknown types String strVal = value.toString(); diff --git a/source/java/org/alfresco/web/bean/BaseDetailsBean.java b/source/java/org/alfresco/web/bean/BaseDetailsBean.java index 763b1915a3..09d0b7b6bb 100644 --- a/source/java/org/alfresco/web/bean/BaseDetailsBean.java +++ b/source/java/org/alfresco/web/bean/BaseDetailsBean.java @@ -16,6 +16,7 @@ */ package org.alfresco.web.bean; +import java.io.Serializable; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -25,16 +26,22 @@ import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.transaction.UserTransaction; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateImageResolver; import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.namespace.QName; import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.bean.actions.handlers.SimpleWorkflowHandler; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; import org.alfresco.web.ui.common.Utils; import org.alfresco.web.ui.common.Utils.URLMode; +import org.alfresco.web.ui.common.component.UIActionLink; import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent; /** @@ -58,11 +65,27 @@ public abstract class BaseDetailsBean /** OwnableService bean reference */ protected OwnableService ownableService; + /** CopyService bean reference */ + protected CopyService copyService; + /** Selected template Id */ protected String template; + /** The map of workflow properties */ + protected Map workflowProperties; + protected Map panels = new HashMap(4, 1.0f); + private static final String MSG_ERROR_WORKFLOW_REJECT = "error_workflow_reject"; + private static final String MSG_ERROR_WORKFLOW_APPROVE = "error_workflow_approve"; + private static final String MSG_ERROR_UPDATE_SIMPLEWORKFLOW = "error_update_simpleworkflow"; + + public BaseDetailsBean() + { + // initial state of some panels that don't use the default + panels.put("workflow-panel", false); + panels.put("category-panel", false); + } // ------------------------------------------------------------------------------ // Bean property getters and setters @@ -103,6 +126,16 @@ public abstract class BaseDetailsBean this.ownableService = ownableService; } + /** + * Sets the copy service instance the bean should use + * + * @param copyService The CopyService + */ + public void setCopyService(CopyService copyService) + { + this.copyService = copyService; + } + /** * @return Returns the panels expanded state map. */ @@ -249,6 +282,302 @@ public abstract class BaseDetailsBean } }; + /** + * Returns the properties for the attached workflow as a map + * + * @return Properties of the attached workflow, null if there is no workflow + */ + public Map getWorkflowProperties() + { + if (this.workflowProperties == null && + getNode().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + // get the exisiting properties for the document + Map props = getNode().getProperties(); + + String approveStepName = (String)props.get( + ContentModel.PROP_APPROVE_STEP.toString()); + String rejectStepName = (String)props.get( + ContentModel.PROP_REJECT_STEP.toString()); + + Boolean approveMove = (Boolean)props.get( + ContentModel.PROP_APPROVE_MOVE.toString()); + Boolean rejectMove = (Boolean)props.get( + ContentModel.PROP_REJECT_MOVE.toString()); + + NodeRef approveFolder = (NodeRef)props.get( + ContentModel.PROP_APPROVE_FOLDER.toString()); + NodeRef rejectFolder = (NodeRef)props.get( + ContentModel.PROP_REJECT_FOLDER.toString()); + + // put the workflow properties in a separate map for use by the JSP + this.workflowProperties = new HashMap(7); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_STEP_NAME, + approveStepName); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_ACTION, + approveMove ? "move" : "copy"); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_FOLDER, approveFolder); + + if (rejectStepName == null || rejectMove == null || rejectFolder == null) + { + this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT, "no"); + } + else + { + this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT, + "yes"); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_NAME, + rejectStepName); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_ACTION, + rejectMove ? "move" : "copy"); + this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_FOLDER, + rejectFolder); + } + } + + return this.workflowProperties; + } + + /** + * Cancel Workflow Edit dialog + */ + public String cancelWorkflowEdit() + { + // resets the workflow properties map so any changes made + // don't appear to be persisted + this.workflowProperties.clear(); + this.workflowProperties = null; + return "cancel"; + } + + /** + * Saves the details of the workflow stored in workflowProperties + * to the current document + * + * @return The outcome string + */ + public String saveWorkflow() + { + String outcome = "cancel"; + + UserTransaction tx = null; + + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // firstly retrieve all the properties for the current node + Map updateProps = this.nodeService.getProperties( + getNode().getNodeRef()); + + // update the simple workflow properties + + // set the approve step name + updateProps.put(ContentModel.PROP_APPROVE_STEP, + this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_STEP_NAME)); + + // specify whether the approve step will copy or move the content + boolean approveMove = true; + String approveAction = (String)this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_ACTION); + if (approveAction != null && approveAction.equals("copy")) + { + approveMove = false; + } + updateProps.put(ContentModel.PROP_APPROVE_MOVE, Boolean.valueOf(approveMove)); + + // create node ref representation of the destination folder + updateProps.put(ContentModel.PROP_APPROVE_FOLDER, + this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_FOLDER)); + + // determine whether there should be a reject step + boolean requireReject = true; + String rejectStepPresent = (String)this.workflowProperties.get( + SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT); + if (rejectStepPresent != null && rejectStepPresent.equals("no")) + { + requireReject = false; + } + + if (requireReject) + { + // set the reject step name + updateProps.put(ContentModel.PROP_REJECT_STEP, + this.workflowProperties.get(SimpleWorkflowHandler.PROP_REJECT_STEP_NAME)); + + // specify whether the reject step will copy or move the content + boolean rejectMove = true; + String rejectAction = (String)this.workflowProperties.get( + SimpleWorkflowHandler.PROP_REJECT_ACTION); + if (rejectAction != null && rejectAction.equals("copy")) + { + rejectMove = false; + } + updateProps.put(ContentModel.PROP_REJECT_MOVE, Boolean.valueOf(rejectMove)); + + // create node ref representation of the destination folder + updateProps.put(ContentModel.PROP_REJECT_FOLDER, + this.workflowProperties.get(SimpleWorkflowHandler.PROP_REJECT_FOLDER)); + } + else + { + // set all the reject properties to null to signify there should + // be no reject step + updateProps.put(ContentModel.PROP_REJECT_STEP, null); + updateProps.put(ContentModel.PROP_REJECT_MOVE, null); + updateProps.put(ContentModel.PROP_REJECT_FOLDER, null); + } + + // set the properties on the node + this.nodeService.setProperties(getNode().getNodeRef(), updateProps); + + // commit the transaction + tx.commit(); + + // reset the state of the current document so it reflects the changes just made + getNode().reset(); + + outcome = "finish"; + } + catch (Throwable e) + { + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE_SIMPLEWORKFLOW), e.getMessage()), e); + } + + return outcome; + } + + /** + * Returns the name of the approve step of the attached workflow + * + * @return The name of the approve step or null if there is no workflow + */ + public String getApproveStepName() + { + String approveStepName = null; + + if (getNode().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + approveStepName = (String)getNode().getProperties().get( + ContentModel.PROP_APPROVE_STEP.toString()); + } + + return approveStepName; + } + + /** + * Event handler called to handle the approve step of the simple workflow + * + * @param event The event that was triggered + */ + public void approve(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id == null || id.length() == 0) + { + throw new AlfrescoRuntimeException("approve called without an id"); + } + + NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // call the service to perform the approve + WorkflowUtil.approve(docNodeRef, this.nodeService, this.copyService); + + // commit the transaction + tx.commit(); + + // if this was called via the document details dialog we need to reset the document node + if (getNode() != null) + { + getNode().reset(); + } + + // also make sure the UI will get refreshed + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + catch (Throwable e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_APPROVE), e.getMessage()), e); + } + } + + /** + * Returns the name of the reject step of the attached workflow + * + * @return The name of the reject step or null if there is no workflow + */ + public String getRejectStepName() + { + String approveStepName = null; + + if (getNode().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + approveStepName = (String)getNode().getProperties().get( + ContentModel.PROP_REJECT_STEP.toString()); + } + + return approveStepName; + } + + /** + * Event handler called to handle the approve step of the simple workflow + * + * @param event The event that was triggered + */ + public void reject(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id == null || id.length() == 0) + { + throw new AlfrescoRuntimeException("reject called without an id"); + } + + NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); + + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + // call the service to perform the reject + WorkflowUtil.reject(docNodeRef, this.nodeService, this.copyService); + + // commit the transaction + tx.commit(); + + // if this was called via the document details dialog we need to reset the document node + if (getNode() != null) + { + getNode().reset(); + } + + // also make sure the UI will get refreshed + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + catch (Throwable e) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_REJECT), e.getMessage()), e); + } + } // ------------------------------------------------------------------------------ // Action event handlers diff --git a/source/java/org/alfresco/web/bean/BrowseBean.java b/source/java/org/alfresco/web/bean/BrowseBean.java index 4fa7ded19a..d230258b44 100644 --- a/source/java/org/alfresco/web/bean/BrowseBean.java +++ b/source/java/org/alfresco/web/bean/BrowseBean.java @@ -1,1741 +1,1744 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.web.bean; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.faces.application.FacesMessage; -import javax.faces.context.FacesContext; -import javax.faces.event.ActionEvent; -import javax.transaction.UserTransaction; - -import org.alfresco.config.Config; -import org.alfresco.config.ConfigElement; -import org.alfresco.config.ConfigService; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.lock.LockService; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.web.app.AlfrescoNavigationHandler; -import org.alfresco.web.app.Application; -import org.alfresco.web.app.context.IContextListener; -import org.alfresco.web.app.context.UIContextService; -import org.alfresco.web.app.servlet.DownloadContentServlet; -import org.alfresco.web.app.servlet.FacesHelper; -import org.alfresco.web.bean.repository.MapNode; -import org.alfresco.web.bean.repository.Node; -import org.alfresco.web.bean.repository.NodePropertyResolver; -import org.alfresco.web.bean.repository.QNameNodeMap; -import org.alfresco.web.bean.repository.Repository; -import org.alfresco.web.bean.spaces.CreateSpaceWizard; -import org.alfresco.web.config.ViewsConfigElement; -import org.alfresco.web.ui.common.Utils; -import org.alfresco.web.ui.common.Utils.URLMode; -import org.alfresco.web.ui.common.component.IBreadcrumbHandler; -import org.alfresco.web.ui.common.component.UIActionLink; -import org.alfresco.web.ui.common.component.UIBreadcrumb; -import org.alfresco.web.ui.common.component.UIModeList; -import org.alfresco.web.ui.common.component.UIStatusMessage; -import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent; -import org.alfresco.web.ui.common.component.data.UIRichList; -import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; -import org.alfresco.web.ui.repo.component.UINodeDescendants; -import org.alfresco.web.ui.repo.component.UINodePath; -import org.alfresco.web.ui.repo.component.UISimpleSearch; -import org.apache.log4j.Logger; -import org.apache.log4j.Priority; - -/** - * Bean providing properties and behaviour for the main folder/document browse screen and - * search results screens. - * - * @author Kevin Roast - */ -public class BrowseBean implements IContextListener -{ - // ------------------------------------------------------------------------------ - // Construction - - /** - * Default Constructor - */ - public BrowseBean() - { - UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); - - initFromClientConfig(); - } - - - // ------------------------------------------------------------------------------ - // Bean property getters and setters - - /** - * @param nodeService The NodeService to set. - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param searchService The Searcher to set. - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * @param lockService The Lock Service to set. - */ - public void setLockService(LockService lockService) - { - this.lockService = lockService; - } - - /** - * @param navigator The NavigationBean to set. - */ - public void setNavigator(NavigationBean navigator) - { - this.navigator = navigator; - } - - /** - * @param dictionaryService The DictionaryService to set. - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @param fileFolderService The FileFolderService to set. - */ - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - /** - * @return Returns the browse View mode. See UIRichList - */ - public String getBrowseViewMode() - { - return this.browseViewMode; - } - - /** - * @param browseViewMode The browse View mode to set. See UIRichList. - */ - public void setBrowseViewMode(String browseViewMode) - { - this.browseViewMode = browseViewMode; - } - - /** - * @return Returns true if dashboard view is available for the current node. - */ - public boolean isDashboardView() - { - return this.dashboardView; - } - - /** - * @param dashboardView The dashboard view mode to set. - */ - public void setDashboardView(boolean dashboardView) - { - this.dashboardView = dashboardView; - if (dashboardView == true) - { - FacesContext fc = FacesContext.getCurrentInstance(); - fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dashboard"); - } - } - - public int getPageSizeContent() - { - return this.pageSizeContent; - } - - public void setPageSizeContent(int pageSizeContent) - { - this.pageSizeContent = pageSizeContent; - this.pageSizeContentStr = Integer.toString(pageSizeContent); - } - - public int getPageSizeSpaces() - { - return this.pageSizeSpaces; - } - - public void setPageSizeSpaces(int pageSizeSpaces) - { - this.pageSizeSpaces = pageSizeSpaces; - this.pageSizeSpacesStr = Integer.toString(pageSizeSpaces); - } - - public String getPageSizeContentStr() - { - return this.pageSizeContentStr; - } - - public void setPageSizeContentStr(String pageSizeContentStr) - { - this.pageSizeContentStr = pageSizeContentStr; - } - - public String getPageSizeSpacesStr() - { - return this.pageSizeSpacesStr; - } - - public void setPageSizeSpacesStr(String pageSizeSpacesStr) - { - this.pageSizeSpacesStr = pageSizeSpacesStr; - } - - /** - * @return Returns the minimum length of a valid search string. - */ - public int getMinimumSearchLength() - { - return Application.getClientConfig(FacesContext.getCurrentInstance()). - getSearchMinimum(); - } - - /** - * @return Returns the panels expanded state map. - */ - public Map getPanels() - { - return this.panels; - } - - /** - * @param panels The panels expanded state map. - */ - public void setPanels(Map panels) - { - this.panels = panels; - } - - /** - * @return Returns the Space Node being used for the current browse screen action. - */ - public Node getActionSpace() - { - return this.actionSpace; - } - - /** - * @param actionSpace Set the Space Node to be used for the current browse screen action. - */ - public void setActionSpace(Node actionSpace) - { - if (actionSpace != null) - { - for (NodeEventListener listener : getNodeEventListeners()) - { - listener.created(actionSpace, actionSpace.getType()); - } - } - this.actionSpace = actionSpace; - } - - /** - * @return The document node being used for the current operation - */ - public Node getDocument() - { - return this.document; - } - - /** - * @param document The document node to be used for the current operation - */ - public void setDocument(Node document) - { - if (document != null) - { - for (NodeEventListener listener : getNodeEventListeners()) - { - listener.created(document, document.getType()); - } - } - this.document = document; - } - - /** - * @param contentRichList The contentRichList to set. - */ - public void setContentRichList(UIRichList browseRichList) - { - this.contentRichList = browseRichList; - if (this.contentRichList != null) - { - this.contentRichList.setInitialSortColumn( - this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); - this.contentRichList.setInitialSortDescending( - this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE)); - } - // special case to handle an External Access URL - // these URLs restart the JSF lifecycle but an old UIRichList is restored from - // the component tree - which needs clearing "late" in the lifecycle process - if (externalForceRefresh) - { - this.contentRichList.setValue(null); - externalForceRefresh = false; - } - } - - /** - * @return Returns the contentRichList. - */ - public UIRichList getContentRichList() - { - return this.contentRichList; - } - - /** - * @param spacesRichList The spacesRichList to set. - */ - public void setSpacesRichList(UIRichList detailsRichList) - { - this.spacesRichList = detailsRichList; - if (this.spacesRichList != null) - { - // set the initial sort column and direction - this.spacesRichList.setInitialSortColumn( - this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); - this.spacesRichList.setInitialSortDescending( - this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE)); - } - if (externalForceRefresh) - { - this.spacesRichList.setValue(null); - } - } - - /** - * @return Returns the spacesRichList. - */ - public UIRichList getSpacesRichList() - { - return this.spacesRichList; - } - - /** - * @return Returns the statusMessage component. - */ - public UIStatusMessage getStatusMessage() - { - return this.statusMessage; - } - - /** - * @param statusMessage The statusMessage component to set. - */ - public void setStatusMessage(UIStatusMessage statusMessage) - { - this.statusMessage = statusMessage; - } - - /** - * @return Returns the deleteMessage. - */ - public String getDeleteMessage() - { - return this.deleteMessage; - } - - /** - * @param deleteMessage The deleteMessage to set. - */ - public void setDeleteMessage(String deleteMessage) - { - this.deleteMessage = deleteMessage; - } - - /** - * Page accessed bean method to get the container nodes currently being browsed - * - * @return List of container Node objects for the current browse location - */ - public List getNodes() - { - // the references to container nodes and content nodes are transient for one use only - // we do this so we only query/search once - as we cannot distinguish between node types - // until after the query. The logic is a bit confusing but otherwise we would need to - // perform the same query or search twice for every screen refresh. - if (this.containerNodes == null) - { - if (this.navigator.getSearchContext() == null) - { - queryBrowseNodes(this.navigator.getCurrentNodeId()); - } - else - { - searchBrowseNodes(this.navigator.getSearchContext()); - } - } - List result = this.containerNodes; - - // we clear the member variable during invalidateComponents() - - return result; - } - - /** - * Page accessed bean method to get the content nodes currently being browsed - * - * @return List of content Node objects for the current browse location - */ - public List getContent() - { - // see comment in getNodes() above for reasoning here - if (this.contentNodes == null) - { - if (this.navigator.getSearchContext() == null) - { - queryBrowseNodes(this.navigator.getCurrentNodeId()); - } - else - { - searchBrowseNodes(this.navigator.getSearchContext()); - } - } - List result = this.contentNodes; - - // we clear the member variable during invalidateComponents() - - return result; - } - - /** - * Setup the common properties required at data-binding time. - *

- * These are properties used by components on the page when iterating over the nodes. - * The properties are available as the Node is a Map so they can be accessed directly - * by name. Information such as download URL, size and filetype are provided etc. - *

- * We use a set of anonymous inner classes to provide the implemention for the property - * getters. The interfaces are only called when the properties are first requested. - * - * @param node Node to add the properties too - */ - public void setupCommonBindingProperties(Node node) - { - // special properties to be used by the value binding components on the page - node.addPropertyResolver("url", this.resolverUrl); - node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl); - node.addPropertyResolver("cifsPath", this.resolverCifsPath); - node.addPropertyResolver("fileType16", this.resolverFileType16); - node.addPropertyResolver("fileType32", this.resolverFileType32); - node.addPropertyResolver("size", this.resolverSize); - } - - - // ------------------------------------------------------------------------------ - // IContextListener implementation - - /** - * @see org.alfresco.web.app.context.IContextListener#contextUpdated() - */ - public void contextUpdated() - { - invalidateComponents(); - } - - - // ------------------------------------------------------------------------------ - // NodeEventListener listeners - - /** - * Add a listener to those called by the BrowseBean when nodes are created - */ - public void addNodeEventListener(NodeEventListener listener) - { - getNodeEventListeners().add(listener); - } - - /** - * Remove a listener from the list of those called by BrowseBean - */ - public void removeNodeEventListener(NodeEventListener listener) - { - getNodeEventListeners().remove(listener); - } - - - // ------------------------------------------------------------------------------ - // Navigation action event handlers - - /** - * Change the current view mode based on user selection - * - * @param event ActionEvent - */ - public void viewModeChanged(ActionEvent event) - { - UIModeList viewList = (UIModeList)event.getComponent(); - - // get the view mode ID - String viewMode = viewList.getValue().toString(); - - if (VIEWMODE_DASHBOARD.equals(viewMode) == false) - { - // set the page size based on the style of display - int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, viewMode); - setPageSizeContent(pageSize); - setPageSizeSpaces(pageSize); - - if (logger.isDebugEnabled()) - logger.debug("Browse view page size set to: " + pageSize); - - setDashboardView(false); - - // push the view mode into the lists - setBrowseViewMode(viewMode); - - navigateBrowseScreen(); - } - else - { - // special case for Dashboard view - setDashboardView(true); - } - } - - - // ------------------------------------------------------------------------------ - // Helper methods - - /** - * Query a list of nodes for the specified parent node Id - * - * @param parentNodeId Id of the parent node or null for the root node - */ - private void queryBrowseNodes(String parentNodeId) - { - long startTime = 0; - if (logger.isDebugEnabled()) - startTime = System.currentTimeMillis(); - - UserTransaction tx = null; - try - { - FacesContext context = FacesContext.getCurrentInstance(); - tx = Repository.getUserTransaction(context, true); - tx.begin(); - - NodeRef parentRef; - if (parentNodeId == null) - { - // no specific parent node specified - use the root node - parentRef = this.nodeService.getRootNode(Repository.getStoreRef()); - } - else - { - // build a NodeRef for the specified Id and our store - parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId); - } - - List childRefs = this.nodeService.getChildAssocs(parentRef, - ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); - this.containerNodes = new ArrayList(childRefs.size()); - this.contentNodes = new ArrayList(childRefs.size()); - for (ChildAssociationRef ref: childRefs) - { - // create our Node representation from the NodeRef - NodeRef nodeRef = ref.getChildRef(); - - if (this.nodeService.exists(nodeRef)) - { - // find it's type so we can see if it's a node we are interested in - QName type = this.nodeService.getType(nodeRef); - - // make sure the type is defined in the data dictionary - TypeDefinition typeDef = this.dictionaryService.getType(type); - - if (typeDef != null) - { - MapNode node = null; - - // look for Space folder node - if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && - this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) - { - // create our Node representation - node = new MapNode(nodeRef, this.nodeService, true); - node.addPropertyResolver("icon", this.resolverSpaceIcon); - node.addPropertyResolver("smallIcon", this.resolverSmallIcon); - - this.containerNodes.add(node); - } - // look for File content node - else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) - { - // create our Node representation - node = new MapNode(nodeRef, this.nodeService, true); - setupCommonBindingProperties(node); - - this.contentNodes.add(node); - } - // look for File Link object node - else if (ContentModel.TYPE_FILELINK.equals(type)) - { - // create our File Link Node representation - node = new MapNode(nodeRef, this.nodeService, true); - node.addPropertyResolver("url", this.resolverLinkUrl); - node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl); - node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath); - node.addPropertyResolver("fileType16", this.resolverFileType16); - node.addPropertyResolver("fileType32", this.resolverFileType32); - node.addPropertyResolver("size", this.resolverSize); - - this.contentNodes.add(node); - } - else if (ContentModel.TYPE_FOLDERLINK.equals(type)) - { - // create our Folder Link Node representation - node = new MapNode(nodeRef, this.nodeService, true); - node.addPropertyResolver("icon", this.resolverSpaceIcon); - node.addPropertyResolver("smallIcon", this.resolverSmallIcon); - - this.containerNodes.add(node); - } - - // inform any listeners that a Node wrapper has been created - if (node != null) - { - for (NodeEventListener listener : getNodeEventListeners()) - { - listener.created(node, type); - } - } - } - else - { - if (logger.isEnabledFor(Priority.WARN)) - logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); - } - } - } - - // commit the transaction - tx.commit(); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); - this.containerNodes = Collections.emptyList(); - this.contentNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - catch (Throwable err) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); - this.containerNodes = Collections.emptyList(); - this.contentNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - - if (logger.isDebugEnabled()) - { - long endTime = System.currentTimeMillis(); - logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); - } - } - - /** - * Search for a list of nodes using the specific search context - * - * @param searchContext To use to perform the search - */ - private void searchBrowseNodes(SearchContext searchContext) - { - long startTime = 0; - if (logger.isDebugEnabled()) - startTime = System.currentTimeMillis(); - - // get the searcher object to build the query - String query = searchContext.buildQuery(getMinimumSearchLength()); - if (query == null) - { - // failed to build a valid query, the user probably did not enter the - // minimum text required to construct a valid search - Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_MINIMUM), - new Object[] {getMinimumSearchLength()})); - this.containerNodes = Collections.emptyList(); - this.contentNodes = Collections.emptyList(); - return; - } - - // perform the search against the repo - UserTransaction tx = null; - ResultSet results = null; - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); - tx.begin(); - - // Limit search to the first 100 matches - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery(query); - sp.addStore(Repository.getStoreRef()); - - int searchLimit = Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMaxResults(); - if(searchLimit > 0) - { - sp.setLimitBy(LimitBy.FINAL_SIZE); - sp.setLimit(searchLimit); - } - - results = this.searchService.query(sp); - if (logger.isDebugEnabled()) - logger.debug("Search results returned: " + results.length()); - - // create a list of items from the results - this.containerNodes = new ArrayList(results.length()); - this.contentNodes = new ArrayList(results.length()); - if (results.length() != 0) - { - for (ResultSetRow row: results) - { - NodeRef nodeRef = row.getNodeRef(); - - if (this.nodeService.exists(nodeRef)) - { - // find it's type so we can see if it's a node we are interested in - QName type = this.nodeService.getType(nodeRef); - - // make sure the type is defined in the data dictionary - TypeDefinition typeDef = this.dictionaryService.getType(type); - - if (typeDef != null) - { - MapNode node = null; - - // look for Space or File nodes - if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && - this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) - { - // create our Node representation - node = new MapNode(nodeRef, this.nodeService, false); - - node.addPropertyResolver("path", this.resolverPath); - node.addPropertyResolver("displayPath", this.resolverDisplayPath); - node.addPropertyResolver("icon", this.resolverSpaceIcon); - node.addPropertyResolver("smallIcon", this.resolverSmallIcon); - - this.containerNodes.add(node); - } - else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) - { - // create our Node representation - node = new MapNode(nodeRef, this.nodeService, false); - - setupCommonBindingProperties(node); - - node.addPropertyResolver("path", this.resolverPath); - node.addPropertyResolver("displayPath", this.resolverDisplayPath); - - this.contentNodes.add(node); - } - // look for File Link object node - else if (ContentModel.TYPE_FILELINK.equals(type)) - { - // create our File Link Node representation - node = new MapNode(nodeRef, this.nodeService, false); - node.addPropertyResolver("url", this.resolverLinkUrl); - node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl); - node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath); - node.addPropertyResolver("fileType16", this.resolverFileType16); - node.addPropertyResolver("fileType32", this.resolverFileType32); - node.addPropertyResolver("size", this.resolverSize); - node.addPropertyResolver("path", this.resolverPath); - node.addPropertyResolver("displayPath", this.resolverDisplayPath); - - this.contentNodes.add(node); - } - else if (ContentModel.TYPE_FOLDERLINK.equals(type)) - { - // create our Folder Link Node representation - node = new MapNode(nodeRef, this.nodeService, false); - node.addPropertyResolver("icon", this.resolverSpaceIcon); - node.addPropertyResolver("smallIcon", this.resolverSmallIcon); - node.addPropertyResolver("path", this.resolverPath); - node.addPropertyResolver("displayPath", this.resolverDisplayPath); - - this.containerNodes.add(node); - } - - // inform any listeners that a Node wrapper has been created - if (node != null) - { - for (NodeEventListener listener : getNodeEventListeners()) - { - listener.created(node, type); - } - } - } - else - { - if (logger.isEnabledFor(Priority.WARN)) - logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); - } - } - else - { - if (logger.isEnabledFor(Priority.WARN)) - logger.warn("Missing object returned from search indexes: id = " + nodeRef + " search query: " + query); - } - } - } - - // commit the transaction - tx.commit(); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}) ); - this.containerNodes = Collections.emptyList(); - this.contentNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - catch (Throwable err) - { - logger.info("Search failed for: " + query); - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH), new Object[] {err.getMessage()}), err ); - this.containerNodes = Collections.emptyList(); - this.contentNodes = Collections.emptyList(); - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - } - finally - { - if (results != null) - { - results.close(); - } - } - - if (logger.isDebugEnabled()) - { - long endTime = System.currentTimeMillis(); - logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); - } - } - - - // ------------------------------------------------------------------------------ - // Property Resolvers - - public NodePropertyResolver resolverDownload = new NodePropertyResolver() { - public Object get(Node node) { - return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); - } - }; - - public NodePropertyResolver resolverUrl = new NodePropertyResolver() { - public Object get(Node node) { - return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); - } - }; - - public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() { - public Object get(Node node) { - return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV); - } - }; - - public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() { - public Object get(Node node) { - return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS); - } - }; - - public NodePropertyResolver resolverLinkDownload = new NodePropertyResolver() { - public Object get(Node node) { - NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); - if (nodeService.exists(destRef) == true) - { - String destName = Repository.getNameForNode(nodeService, destRef); - return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), destName); - } - else - { - // TODO: link object is missing - navigate to a page with appropriate message - return "#"; - } - } - }; - - public NodePropertyResolver resolverLinkUrl = new NodePropertyResolver() { - public Object get(Node node) { - NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); - if (nodeService.exists(destRef) == true) - { - String destName = Repository.getNameForNode(nodeService, destRef); - return DownloadContentServlet.generateBrowserURL(destRef, destName); - } - else - { - // TODO: link object is missing - navigate to a page with appropriate message - return "#"; - } - } - }; - - public NodePropertyResolver resolverLinkWebdavUrl = new NodePropertyResolver() { - public Object get(Node node) { - NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); - if (nodeService.exists(destRef) == true) - { - return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.WEBDAV); - } - else - { - // TODO: link object is missing - navigate to a page with appropriate message - return "#"; - } - } - }; - - public NodePropertyResolver resolverLinkCifsPath = new NodePropertyResolver() { - public Object get(Node node) { - NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); - if (nodeService.exists(destRef) == true) - { - return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.CIFS); - } - else - { - // TODO: link object is missing - navigate to a page with appropriate message - return "#"; - } - } - }; - - public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() { - public Object get(Node node) { - return Utils.getFileTypeImage(node.getName(), true); - } - }; - - public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() { - public Object get(Node node) { - return Utils.getFileTypeImage(node.getName(), false); - } - }; - - public NodePropertyResolver resolverPath = new NodePropertyResolver() { - public Object get(Node node) { - return nodeService.getPath(node.getNodeRef()); - } - }; - - public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() { - public Object get(Node node) { - // TODO: replace this with a method that shows the full display name - not QNames? - return Repository.getDisplayPath( (Path)node.getProperties().get("path") ); - } - }; - - public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() { - public Object get(Node node) { - QNameNodeMap props = (QNameNodeMap)node.getProperties(); - String icon = (String)props.getRaw("app:icon"); - return (icon != null ? icon : CreateSpaceWizard.DEFAULT_SPACE_ICON_NAME); - } - }; - - public NodePropertyResolver resolverSmallIcon = new NodePropertyResolver() { - public Object get(Node node) { - QNameNodeMap props = (QNameNodeMap)node.getProperties(); - String icon = (String)props.getRaw("app:icon"); - return (icon != null ? icon + "-16" : SPACE_SMALL_DEFAULT); - } - }; - - public NodePropertyResolver resolverMimetype = new NodePropertyResolver() { - public Object get(Node node) { - ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); - return (content != null ? content.getMimetype() : null); - } - }; - - public NodePropertyResolver resolverSize = new NodePropertyResolver() { - public Object get(Node node) { - ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); - return (content != null ? new Long(content.getSize()) : 0L); - } - }; - - - // ------------------------------------------------------------------------------ - // Navigation action event handlers - - /** - * Action called from the Simple Search component. - * Sets up the SearchContext object with the values from the simple search menu. - */ - public void search(ActionEvent event) - { - // setup the search text string on the top-level navigation handler - UISimpleSearch search = (UISimpleSearch)event.getComponent(); - this.navigator.setSearchContext(search.getSearchContext()); - - navigateBrowseScreen(); - } - - /** - * Action called to Close the search dialog by returning to the last view node Id - */ - public void closeSearch(ActionEvent event) - { - // set the current node Id ready for page refresh - this.navigator.setCurrentNodeId( this.navigator.getCurrentNodeId() ); - } - - /** - * Update page size based on user selection - */ - public void updateSpacesPageSize(ActionEvent event) - { - try - { - int size = Integer.parseInt(this.pageSizeSpacesStr); - if (size >= 0) - { - this.pageSizeSpaces = size; - } - else - { - // reset to known value if this occurs - this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces); - } - } - catch (NumberFormatException err) - { - // reset to known value if this occurs - this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces); - } - } - - /** - * Update page size based on user selection - */ - public void updateContentPageSize(ActionEvent event) - { - try - { - int size = Integer.parseInt(this.pageSizeContentStr); - if (size >= 0) - { - this.pageSizeContent = size; - } - else - { - // reset to known value if this occurs - this.pageSizeContentStr = Integer.toString(this.pageSizeContent); - } - } - catch (NumberFormatException err) - { - // reset to known value if this occurs - this.pageSizeContentStr = Integer.toString(this.pageSizeContent); - } - } - - /** - * Action called when a folder space is clicked. - * Navigate into the space. - */ - public void clickSpace(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - if (id != null && id.length() != 0) - { - try - { - NodeRef ref = new NodeRef(Repository.getStoreRef(), id); - - // handle special folder link node case - if (ContentModel.TYPE_FOLDERLINK.equals(this.nodeService.getType(ref))) - { - ref = (NodeRef)this.nodeService.getProperty(ref, ContentModel.PROP_LINK_DESTINATION); - } - - clickSpace(ref); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); - } - } - } - - /** - * Action called when a folder space is clicked. - * - * @param nodeRef The node being clicked - */ - public void clickSpace(NodeRef nodeRef) - { - // refresh UI based on node selection - updateUILocation(nodeRef); - } - - /** - * Handler called when a path element is clicked - navigate to the appropriate Space - */ - public void clickSpacePath(ActionEvent event) - { - UINodePath.PathElementEvent pathEvent = (UINodePath.PathElementEvent)event; - NodeRef ref = pathEvent.NodeReference; - try - { - // refresh UI based on node selection - this.updateUILocation(ref); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {ref.getId()}) ); - } - } - - /** - * Action called when a folders direct descendant (in the 'list' browse mode) is clicked. - * Navigate into the the descendant space. - */ - public void clickDescendantSpace(ActionEvent event) - { - UINodeDescendants.NodeSelectedEvent nodeEvent = (UINodeDescendants.NodeSelectedEvent)event; - NodeRef nodeRef = nodeEvent.NodeReference; - if (nodeRef == null) - { - throw new IllegalStateException("NodeRef returned from UINodeDescendants.NodeSelectedEvent cannot be null!"); - } - - if (logger.isDebugEnabled()) - logger.debug("Selected noderef Id: " + nodeRef.getId()); - - try - { - // user can either select a descendant of a node display on the page which means we - // must add the it's parent and itself to the breadcrumb - ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); - - if (logger.isDebugEnabled()) - { - logger.debug("Selected item getPrimaryParent().getChildRef() noderef Id: " + parentAssocRef.getChildRef().getId()); - logger.debug("Selected item getPrimaryParent().getParentRef() noderef Id: " + parentAssocRef.getParentRef().getId()); - logger.debug("Current value getNavigator().getCurrentNodeId() noderef Id: " + this.navigator.getCurrentNodeId()); - } - - if (nodeEvent.IsParent == false) - { - // a descendant of the displayed node was selected - // first refresh based on the parent and add to the breadcrumb - updateUILocation(parentAssocRef.getParentRef()); - - // now add our selected node - updateUILocation(nodeRef); - } - else - { - // else the parent ellipses i.e. the displayed node was selected - updateUILocation(nodeRef); - } - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) ); - } - } - - /** - * Action event called by all Browse actions that need to setup a Space context - * before an action page/wizard is called. The context will be a Node in setActionSpace() which - * can be retrieved on the action page from BrowseBean.getActionSpace(). - * - * @param event ActionEvent - */ - public void setupSpaceAction(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - setupSpaceAction(id, true); - } - - /** - * Public helper to setup action pages with Space context - * - * @param id of the Space node to setup context for - */ - public void setupSpaceAction(String id, boolean invalidate) - { - if (id != null && id.length() != 0) - { - if (logger.isDebugEnabled()) - logger.debug("Setup for action, setting current space to: " + id); - - try - { - // create the node ref, then our node representation - NodeRef ref = new NodeRef(Repository.getStoreRef(), id); - Node node = new Node(ref); - - // resolve icon in-case one has not been set - node.addPropertyResolver("icon", this.resolverSpaceIcon); - - // prepare a node for the action context - setActionSpace(node); - - // setup the dispatch context in case it is required - this.navigator.setupDispatchContext(node); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); - } - } - else - { - setActionSpace(null); - } - - // clear the UI state in preparation for finishing the next action - if (invalidate == true) - { - // use the context service to notify all registered beans - UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); - } - } - - /** - * Acrtion event called by Delete Space actions. We setup the action space as normal, then prepare - * any special case message string to be shown to the user if they are trying to delete specific spaces. - */ - public void setupDeleteAction(ActionEvent event) - { - String message = null; - - setupSpaceAction(event); - - Node node = getActionSpace(); - if (node != null) - { - NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); - if (node.getNodeRef().equals(companyRootRef)) - { - message = Application.getMessage(FacesContext.getCurrentInstance(), MSG_DELETE_COMPANYROOT); - } - } - - setDeleteMessage(message); - } - - /** - * Action event called by all actions that need to setup a Content Document context on the - * BrowseBean before an action page/wizard is called. The context will be a Node in - * setDocument() which can be retrieved on the action page from BrowseBean.getDocument(). - */ - public void setupContentAction(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - setupContentAction(params.get("id"), true); - } - - /** - * Public helper to setup action pages with content context - * - * @param id of the content node to setup context for - */ - public void setupContentAction(String id, boolean invalidate) - { - if (id != null && id.length() != 0) - { - if (logger.isDebugEnabled()) - logger.debug("Setup for action, setting current document to: " + id); - - try - { - // create the node ref, then our node representation - NodeRef ref = new NodeRef(Repository.getStoreRef(), id); - Node node = new Node(ref); - - // store the URL to for downloading the content - if (ContentModel.TYPE_FILELINK.equals(node.getType())) - { - node.addPropertyResolver("url", this.resolverLinkDownload); - } - else - { - node.addPropertyResolver("url", this.resolverDownload); - } - node.addPropertyResolver("fileType32", this.resolverFileType32); - node.addPropertyResolver("mimetype", this.resolverMimetype); - node.addPropertyResolver("size", this.resolverSize); - - for (NodeEventListener listener : getNodeEventListeners()) - { - listener.created(node, node.getType()); - } - - // get hold of the DocumentDetailsBean and reset it - DocumentDetailsBean docDetails = (DocumentDetailsBean)FacesContext.getCurrentInstance(). - getExternalContext().getSessionMap().get("DocumentDetailsBean"); - if (docDetails != null) - { - docDetails.reset(); - } - - // remember the document - setDocument(node); - - // setup the dispatch context in case it is required - this.navigator.setupDispatchContext(node); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); - } - } - else - { - setDocument(null); - } - - // clear the UI state in preparation for finishing the next action - if (invalidate == true) - { - // use the context service to notify all registered beans - UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); - } - } - - /** - * Removes the given node from the breadcrumb i.e. following a delete - * - * @param node The space to remove from the breadcrumb - */ - public void removeSpaceFromBreadcrumb(Node node) - { - List location = navigator.getLocation(); - IBreadcrumbHandler handler = location.get(location.size() - 1); - if (handler instanceof BrowseBreadcrumbHandler) - { - // see if the current breadcrumb location is our node - if ( ((BrowseBreadcrumbHandler)handler).getNodeRef().equals(node.getNodeRef()) == true ) - { - location.remove(location.size() - 1); - - // now work out which node to set the list to refresh against - if (location.size() != 0) - { - handler = location.get(location.size() - 1); - if (handler instanceof BrowseBreadcrumbHandler) - { - // change the current node Id - navigator.setCurrentNodeId(((BrowseBreadcrumbHandler)handler).getNodeRef().getId()); - } - else - { - // TODO: shouldn't do this - but for now the user home dir is the root! - navigator.setCurrentNodeId(Application.getCurrentUser(FacesContext.getCurrentInstance()).getHomeSpaceId()); - } - } - } - } - } - - /** - * Support for refresh of lists via special case for an External Access URL. - * these URLs restart the JSF lifecycle but an old UIRichList is restored from - * the component tree - which needs clearing "late" in the lifecycle process. - */ - public void externalAccessRefresh() - { - this.externalForceRefresh = true; - } - - /** - * Save the state of the panel that was expanded/collapsed - */ - public void expandPanel(ActionEvent event) - { - if (event instanceof ExpandedEvent) - { - String id = event.getComponent().getId(); - this.panels.put(id, ((ExpandedEvent)event).State); - } - } - - - // ------------------------------------------------------------------------------ - // Private helpers - - /** - * Initialise default values from client configuration - */ - private void initFromClientConfig() - { - ConfigService config = Application.getConfigService(FacesContext.getCurrentInstance()); - - this.viewsConfig = (ViewsConfigElement)config.getConfig("Views"). - getConfigElement(ViewsConfigElement.CONFIG_ELEMENT_ID); - - this.browseViewMode = this.viewsConfig.getDefaultView(PAGE_NAME_BROWSE); - int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, this.browseViewMode); - setPageSizeContent(pageSize); - setPageSizeSpaces(pageSize); - } - - /** - * @return the Set of NodeEventListeners registered against this bean - */ - private Set getNodeEventListeners() - { - if (this.nodeEventListeners == null) - { - this.nodeEventListeners = new HashSet(); - - FacesContext fc = FacesContext.getCurrentInstance(); - - Config listenerConfig = Application.getConfigService(fc).getConfig("Node Event Listeners"); - if (listenerConfig != null) - { - ConfigElement listenerElement = listenerConfig.getConfigElement("node-event-listeners"); - if (listenerElement != null) - { - for (ConfigElement child : listenerElement.getChildren()) - { - if (child.getName().equals("listener")) - { - // retrieved the JSF Managed Bean identified in the config - String listenerName = child.getValue().trim(); - Object bean = FacesHelper.getManagedBean(fc, listenerName); - if (bean instanceof NodeEventListener) - { - addNodeEventListener((NodeEventListener)bean); - } - } - } - } - } - } - return this.nodeEventListeners; - } - - /** - * Refresh the UI after a Space selection change. Adds the selected space to the breadcrumb - * location path and also updates the list components in the UI. - * - * @param ref NodeRef of the selected space - */ - public void updateUILocation(NodeRef ref) - { - // get the current breadcrumb location and append a new handler to it - // our handler know the ID of the selected node and the display label for it - List location = this.navigator.getLocation(); - if (location.size() != 0) - { - // attempt to find the ID - if it's already in the breadcrumb then we - // navigate directly to that node - rather than add duplication to the breadcrumb path - boolean foundNode = false; - for (int i=0; i newLocation = new ArrayList(i+1); - //newLocation.addAll(location.subList(0, i + 1)); - //this.navigator.setLocation(newLocation); - // TODO: but instead for now we do this: - int count = location.size(); - for (int n=i+1; n nodeEventListeners = null; - - /** Collapsable Panel state */ - private Map panels = new HashMap(4, 1.0f); - - /** Component references */ - protected UIRichList spacesRichList; - protected UIRichList contentRichList; - private UIStatusMessage statusMessage; - - /** Transient lists of container and content nodes for display */ - protected List containerNodes = null; - protected List contentNodes = null; - - /** The current space and it's properties - if any */ - protected Node actionSpace; - - /** The current document */ - protected Node document; - - /** Special message to display when user deleting certain folders e.g. Company Home */ - private String deleteMessage; - - /** The current browse view mode - set to a well known IRichListRenderer identifier */ - private String browseViewMode; - - /** The current browse view page sizes */ - private int pageSizeSpaces; - private int pageSizeContent; - private String pageSizeSpacesStr; - private String pageSizeContentStr; - - /** True if current space has a dashboard (template) view available */ - private boolean dashboardView; - - private boolean externalForceRefresh = false; -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.config.Config; +import org.alfresco.config.ConfigElement; +import org.alfresco.config.ConfigService; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.web.app.AlfrescoNavigationHandler; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.IContextListener; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.app.servlet.FacesHelper; +import org.alfresco.web.bean.repository.MapNode; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.NodePropertyResolver; +import org.alfresco.web.bean.repository.QNameNodeMap; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.spaces.CreateSpaceWizard; +import org.alfresco.web.config.ViewsConfigElement; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.Utils.URLMode; +import org.alfresco.web.ui.common.component.IBreadcrumbHandler; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.alfresco.web.ui.common.component.UIBreadcrumb; +import org.alfresco.web.ui.common.component.UIModeList; +import org.alfresco.web.ui.common.component.UIStatusMessage; +import org.alfresco.web.ui.common.component.UIPanel.ExpandedEvent; +import org.alfresco.web.ui.common.component.data.UIRichList; +import org.alfresco.web.ui.repo.component.IRepoBreadcrumbHandler; +import org.alfresco.web.ui.repo.component.UINodeDescendants; +import org.alfresco.web.ui.repo.component.UINodePath; +import org.alfresco.web.ui.repo.component.UISimpleSearch; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +/** + * Bean providing properties and behaviour for the main folder/document browse screen and + * search results screens. + * + * @author Kevin Roast + */ +public class BrowseBean implements IContextListener +{ + // ------------------------------------------------------------------------------ + // Construction + + /** + * Default Constructor + */ + public BrowseBean() + { + UIContextService.getInstance(FacesContext.getCurrentInstance()).registerBean(this); + + initFromClientConfig(); + } + + + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param searchService The Searcher to set. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param lockService The Lock Service to set. + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @param dictionaryService The DictionaryService to set. + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param fileFolderService The FileFolderService to set. + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @return Returns the browse View mode. See UIRichList + */ + public String getBrowseViewMode() + { + return this.browseViewMode; + } + + /** + * @param browseViewMode The browse View mode to set. See UIRichList. + */ + public void setBrowseViewMode(String browseViewMode) + { + this.browseViewMode = browseViewMode; + } + + /** + * @return Returns true if dashboard view is available for the current node. + */ + public boolean isDashboardView() + { + return this.dashboardView; + } + + /** + * @param dashboardView The dashboard view mode to set. + */ + public void setDashboardView(boolean dashboardView) + { + this.dashboardView = dashboardView; + if (dashboardView == true) + { + FacesContext fc = FacesContext.getCurrentInstance(); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dashboard"); + } + } + + public int getPageSizeContent() + { + return this.pageSizeContent; + } + + public void setPageSizeContent(int pageSizeContent) + { + this.pageSizeContent = pageSizeContent; + this.pageSizeContentStr = Integer.toString(pageSizeContent); + } + + public int getPageSizeSpaces() + { + return this.pageSizeSpaces; + } + + public void setPageSizeSpaces(int pageSizeSpaces) + { + this.pageSizeSpaces = pageSizeSpaces; + this.pageSizeSpacesStr = Integer.toString(pageSizeSpaces); + } + + public String getPageSizeContentStr() + { + return this.pageSizeContentStr; + } + + public void setPageSizeContentStr(String pageSizeContentStr) + { + this.pageSizeContentStr = pageSizeContentStr; + } + + public String getPageSizeSpacesStr() + { + return this.pageSizeSpacesStr; + } + + public void setPageSizeSpacesStr(String pageSizeSpacesStr) + { + this.pageSizeSpacesStr = pageSizeSpacesStr; + } + + /** + * @return Returns the minimum length of a valid search string. + */ + public int getMinimumSearchLength() + { + return Application.getClientConfig(FacesContext.getCurrentInstance()). + getSearchMinimum(); + } + + /** + * @return Returns the panels expanded state map. + */ + public Map getPanels() + { + return this.panels; + } + + /** + * @param panels The panels expanded state map. + */ + public void setPanels(Map panels) + { + this.panels = panels; + } + + /** + * @return Returns the Space Node being used for the current browse screen action. + */ + public Node getActionSpace() + { + return this.actionSpace; + } + + /** + * @param actionSpace Set the Space Node to be used for the current browse screen action. + */ + public void setActionSpace(Node actionSpace) + { + if (actionSpace != null) + { + for (NodeEventListener listener : getNodeEventListeners()) + { + listener.created(actionSpace, actionSpace.getType()); + } + } + this.actionSpace = actionSpace; + } + + /** + * @return The document node being used for the current operation + */ + public Node getDocument() + { + return this.document; + } + + /** + * @param document The document node to be used for the current operation + */ + public void setDocument(Node document) + { + if (document != null) + { + for (NodeEventListener listener : getNodeEventListeners()) + { + listener.created(document, document.getType()); + } + } + this.document = document; + } + + /** + * @param contentRichList The contentRichList to set. + */ + public void setContentRichList(UIRichList browseRichList) + { + this.contentRichList = browseRichList; + if (this.contentRichList != null) + { + this.contentRichList.setInitialSortColumn( + this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); + this.contentRichList.setInitialSortDescending( + this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE)); + } + // special case to handle an External Access URL + // these URLs restart the JSF lifecycle but an old UIRichList is restored from + // the component tree - which needs clearing "late" in the lifecycle process + if (externalForceRefresh) + { + this.contentRichList.setValue(null); + externalForceRefresh = false; + } + } + + /** + * @return Returns the contentRichList. + */ + public UIRichList getContentRichList() + { + return this.contentRichList; + } + + /** + * @param spacesRichList The spacesRichList to set. + */ + public void setSpacesRichList(UIRichList detailsRichList) + { + this.spacesRichList = detailsRichList; + if (this.spacesRichList != null) + { + // set the initial sort column and direction + this.spacesRichList.setInitialSortColumn( + this.viewsConfig.getDefaultSortColumn(PAGE_NAME_BROWSE)); + this.spacesRichList.setInitialSortDescending( + this.viewsConfig.hasDescendingSort(PAGE_NAME_BROWSE)); + } + if (externalForceRefresh) + { + this.spacesRichList.setValue(null); + } + } + + /** + * @return Returns the spacesRichList. + */ + public UIRichList getSpacesRichList() + { + return this.spacesRichList; + } + + /** + * @return Returns the statusMessage component. + */ + public UIStatusMessage getStatusMessage() + { + return this.statusMessage; + } + + /** + * @param statusMessage The statusMessage component to set. + */ + public void setStatusMessage(UIStatusMessage statusMessage) + { + this.statusMessage = statusMessage; + } + + /** + * @return Returns the deleteMessage. + */ + public String getDeleteMessage() + { + return this.deleteMessage; + } + + /** + * @param deleteMessage The deleteMessage to set. + */ + public void setDeleteMessage(String deleteMessage) + { + this.deleteMessage = deleteMessage; + } + + /** + * Page accessed bean method to get the container nodes currently being browsed + * + * @return List of container Node objects for the current browse location + */ + public List getNodes() + { + // the references to container nodes and content nodes are transient for one use only + // we do this so we only query/search once - as we cannot distinguish between node types + // until after the query. The logic is a bit confusing but otherwise we would need to + // perform the same query or search twice for every screen refresh. + if (this.containerNodes == null) + { + if (this.navigator.getSearchContext() == null) + { + queryBrowseNodes(this.navigator.getCurrentNodeId()); + } + else + { + searchBrowseNodes(this.navigator.getSearchContext()); + } + } + List result = this.containerNodes; + + // we clear the member variable during invalidateComponents() + + return result; + } + + /** + * Page accessed bean method to get the content nodes currently being browsed + * + * @return List of content Node objects for the current browse location + */ + public List getContent() + { + // see comment in getNodes() above for reasoning here + if (this.contentNodes == null) + { + if (this.navigator.getSearchContext() == null) + { + queryBrowseNodes(this.navigator.getCurrentNodeId()); + } + else + { + searchBrowseNodes(this.navigator.getSearchContext()); + } + } + List result = this.contentNodes; + + // we clear the member variable during invalidateComponents() + + return result; + } + + /** + * Setup the common properties required at data-binding time. + *

+ * These are properties used by components on the page when iterating over the nodes. + * The properties are available as the Node is a Map so they can be accessed directly + * by name. Information such as download URL, size and filetype are provided etc. + *

+ * We use a set of anonymous inner classes to provide the implemention for the property + * getters. The interfaces are only called when the properties are first requested. + * + * @param node Node to add the properties too + */ + public void setupCommonBindingProperties(Node node) + { + // special properties to be used by the value binding components on the page + node.addPropertyResolver("url", this.resolverUrl); + node.addPropertyResolver("webdavUrl", this.resolverWebdavUrl); + node.addPropertyResolver("cifsPath", this.resolverCifsPath); + node.addPropertyResolver("fileType16", this.resolverFileType16); + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("size", this.resolverSize); + } + + + // ------------------------------------------------------------------------------ + // IContextListener implementation + + /** + * @see org.alfresco.web.app.context.IContextListener#contextUpdated() + */ + public void contextUpdated() + { + invalidateComponents(); + } + + + // ------------------------------------------------------------------------------ + // NodeEventListener listeners + + /** + * Add a listener to those called by the BrowseBean when nodes are created + */ + public void addNodeEventListener(NodeEventListener listener) + { + getNodeEventListeners().add(listener); + } + + /** + * Remove a listener from the list of those called by BrowseBean + */ + public void removeNodeEventListener(NodeEventListener listener) + { + getNodeEventListeners().remove(listener); + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Change the current view mode based on user selection + * + * @param event ActionEvent + */ + public void viewModeChanged(ActionEvent event) + { + UIModeList viewList = (UIModeList)event.getComponent(); + + // get the view mode ID + String viewMode = viewList.getValue().toString(); + + if (VIEWMODE_DASHBOARD.equals(viewMode) == false) + { + // set the page size based on the style of display + int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, viewMode); + setPageSizeContent(pageSize); + setPageSizeSpaces(pageSize); + + if (logger.isDebugEnabled()) + logger.debug("Browse view page size set to: " + pageSize); + + setDashboardView(false); + + // push the view mode into the lists + setBrowseViewMode(viewMode); + + // setup dispatch context for custom views + this.navigator.setupDispatchContext(this.navigator.getCurrentNode()); + + navigateBrowseScreen(); + } + else + { + // special case for Dashboard view + setDashboardView(true); + } + } + + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Query a list of nodes for the specified parent node Id + * + * @param parentNodeId Id of the parent node or null for the root node + */ + private void queryBrowseNodes(String parentNodeId) + { + long startTime = 0; + if (logger.isDebugEnabled()) + startTime = System.currentTimeMillis(); + + UserTransaction tx = null; + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context, true); + tx.begin(); + + NodeRef parentRef; + if (parentNodeId == null) + { + // no specific parent node specified - use the root node + parentRef = this.nodeService.getRootNode(Repository.getStoreRef()); + } + else + { + // build a NodeRef for the specified Id and our store + parentRef = new NodeRef(Repository.getStoreRef(), parentNodeId); + } + + List childRefs = this.nodeService.getChildAssocs(parentRef, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + this.containerNodes = new ArrayList(childRefs.size()); + this.contentNodes = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref: childRefs) + { + // create our Node representation from the NodeRef + NodeRef nodeRef = ref.getChildRef(); + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + MapNode node = null; + + // look for Space folder node + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) == true && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + // create our Node representation + node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + node.addPropertyResolver("smallIcon", this.resolverSmallIcon); + + this.containerNodes.add(node); + } + // look for File content node + else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + // create our Node representation + node = new MapNode(nodeRef, this.nodeService, true); + setupCommonBindingProperties(node); + + this.contentNodes.add(node); + } + // look for File Link object node + else if (ContentModel.TYPE_FILELINK.equals(type)) + { + // create our File Link Node representation + node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("url", this.resolverLinkUrl); + node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl); + node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath); + node.addPropertyResolver("fileType16", this.resolverFileType16); + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("size", this.resolverSize); + + this.contentNodes.add(node); + } + else if (ContentModel.TYPE_FOLDERLINK.equals(type)) + { + // create our Folder Link Node representation + node = new MapNode(nodeRef, this.nodeService, true); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + node.addPropertyResolver("smallIcon", this.resolverSmallIcon); + + this.containerNodes.add(node); + } + + // inform any listeners that a Node wrapper has been created + if (node != null) + { + for (NodeEventListener listener : getNodeEventListeners()) + { + listener.created(node, type); + } + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); + } + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}), refErr ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + + if (logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); + } + } + + /** + * Search for a list of nodes using the specific search context + * + * @param searchContext To use to perform the search + */ + private void searchBrowseNodes(SearchContext searchContext) + { + long startTime = 0; + if (logger.isDebugEnabled()) + startTime = System.currentTimeMillis(); + + // get the searcher object to build the query + String query = searchContext.buildQuery(getMinimumSearchLength()); + if (query == null) + { + // failed to build a valid query, the user probably did not enter the + // minimum text required to construct a valid search + Utils.addErrorMessage(MessageFormat.format(Application.getMessage(FacesContext.getCurrentInstance(), MSG_SEARCH_MINIMUM), + new Object[] {getMinimumSearchLength()})); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + return; + } + + // perform the search against the repo + UserTransaction tx = null; + ResultSet results = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + // Limit search to the first 100 matches + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery(query); + sp.addStore(Repository.getStoreRef()); + + int searchLimit = Application.getClientConfig(FacesContext.getCurrentInstance()).getSearchMaxResults(); + if(searchLimit > 0) + { + sp.setLimitBy(LimitBy.FINAL_SIZE); + sp.setLimit(searchLimit); + } + + results = this.searchService.query(sp); + if (logger.isDebugEnabled()) + logger.debug("Search results returned: " + results.length()); + + // create a list of items from the results + this.containerNodes = new ArrayList(results.length()); + this.contentNodes = new ArrayList(results.length()); + if (results.length() != 0) + { + for (ResultSetRow row: results) + { + NodeRef nodeRef = row.getNodeRef(); + + if (this.nodeService.exists(nodeRef)) + { + // find it's type so we can see if it's a node we are interested in + QName type = this.nodeService.getType(nodeRef); + + // make sure the type is defined in the data dictionary + TypeDefinition typeDef = this.dictionaryService.getType(type); + + if (typeDef != null) + { + MapNode node = null; + + // look for Space or File nodes + if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && + this.dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER) == false) + { + // create our Node representation + node = new MapNode(nodeRef, this.nodeService, false); + + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + node.addPropertyResolver("smallIcon", this.resolverSmallIcon); + + this.containerNodes.add(node); + } + else if (this.dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT)) + { + // create our Node representation + node = new MapNode(nodeRef, this.nodeService, false); + + setupCommonBindingProperties(node); + + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + + this.contentNodes.add(node); + } + // look for File Link object node + else if (ContentModel.TYPE_FILELINK.equals(type)) + { + // create our File Link Node representation + node = new MapNode(nodeRef, this.nodeService, false); + node.addPropertyResolver("url", this.resolverLinkUrl); + node.addPropertyResolver("webdavUrl", this.resolverLinkWebdavUrl); + node.addPropertyResolver("cifsPath", this.resolverLinkCifsPath); + node.addPropertyResolver("fileType16", this.resolverFileType16); + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("size", this.resolverSize); + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + + this.contentNodes.add(node); + } + else if (ContentModel.TYPE_FOLDERLINK.equals(type)) + { + // create our Folder Link Node representation + node = new MapNode(nodeRef, this.nodeService, false); + node.addPropertyResolver("icon", this.resolverSpaceIcon); + node.addPropertyResolver("smallIcon", this.resolverSmallIcon); + node.addPropertyResolver("path", this.resolverPath); + node.addPropertyResolver("displayPath", this.resolverDisplayPath); + + this.containerNodes.add(node); + } + + // inform any listeners that a Node wrapper has been created + if (node != null) + { + for (NodeEventListener listener : getNodeEventListeners()) + { + listener.created(node, type); + } + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type); + } + } + else + { + if (logger.isEnabledFor(Priority.WARN)) + logger.warn("Missing object returned from search indexes: id = " + nodeRef + " search query: " + query); + } + } + } + + // commit the transaction + tx.commit(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {refErr.getNodeRef()}), refErr ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + catch (Throwable err) + { + logger.info("Search failed for: " + query); + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_SEARCH), new Object[] {err.getMessage()}), err ); + this.containerNodes = Collections.emptyList(); + this.contentNodes = Collections.emptyList(); + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + } + finally + { + if (results != null) + { + results.close(); + } + } + + if (logger.isDebugEnabled()) + { + long endTime = System.currentTimeMillis(); + logger.debug("Time to query and build map nodes: " + (endTime - startTime) + "ms"); + } + } + + + // ------------------------------------------------------------------------------ + // Property Resolvers + + public NodePropertyResolver resolverDownload = new NodePropertyResolver() { + public Object get(Node node) { + return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); + } + }; + + public NodePropertyResolver resolverUrl = new NodePropertyResolver() { + public Object get(Node node) { + return DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); + } + }; + + public NodePropertyResolver resolverWebdavUrl = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.WEBDAV); + } + }; + + public NodePropertyResolver resolverCifsPath = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.generateURL(FacesContext.getCurrentInstance(), node, URLMode.CIFS); + } + }; + + public NodePropertyResolver resolverLinkDownload = new NodePropertyResolver() { + public Object get(Node node) { + NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); + if (nodeService.exists(destRef) == true) + { + String destName = Repository.getNameForNode(nodeService, destRef); + return DownloadContentServlet.generateDownloadURL(node.getNodeRef(), destName); + } + else + { + // TODO: link object is missing - navigate to a page with appropriate message + return "#"; + } + } + }; + + public NodePropertyResolver resolverLinkUrl = new NodePropertyResolver() { + public Object get(Node node) { + NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); + if (nodeService.exists(destRef) == true) + { + String destName = Repository.getNameForNode(nodeService, destRef); + return DownloadContentServlet.generateBrowserURL(destRef, destName); + } + else + { + // TODO: link object is missing - navigate to a page with appropriate message + return "#"; + } + } + }; + + public NodePropertyResolver resolverLinkWebdavUrl = new NodePropertyResolver() { + public Object get(Node node) { + NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); + if (nodeService.exists(destRef) == true) + { + return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.WEBDAV); + } + else + { + // TODO: link object is missing - navigate to a page with appropriate message + return "#"; + } + } + }; + + public NodePropertyResolver resolverLinkCifsPath = new NodePropertyResolver() { + public Object get(Node node) { + NodeRef destRef = (NodeRef)node.getProperties().get(ContentModel.PROP_LINK_DESTINATION); + if (nodeService.exists(destRef) == true) + { + return Utils.generateURL(FacesContext.getCurrentInstance(), new Node(destRef), URLMode.CIFS); + } + else + { + // TODO: link object is missing - navigate to a page with appropriate message + return "#"; + } + } + }; + + public NodePropertyResolver resolverFileType16 = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.getFileTypeImage(node.getName(), true); + } + }; + + public NodePropertyResolver resolverFileType32 = new NodePropertyResolver() { + public Object get(Node node) { + return Utils.getFileTypeImage(node.getName(), false); + } + }; + + public NodePropertyResolver resolverPath = new NodePropertyResolver() { + public Object get(Node node) { + return nodeService.getPath(node.getNodeRef()); + } + }; + + public NodePropertyResolver resolverDisplayPath = new NodePropertyResolver() { + public Object get(Node node) { + // TODO: replace this with a method that shows the full display name - not QNames? + return Repository.getDisplayPath( (Path)node.getProperties().get("path") ); + } + }; + + public NodePropertyResolver resolverSpaceIcon = new NodePropertyResolver() { + public Object get(Node node) { + QNameNodeMap props = (QNameNodeMap)node.getProperties(); + String icon = (String)props.getRaw("app:icon"); + return (icon != null ? icon : CreateSpaceWizard.DEFAULT_SPACE_ICON_NAME); + } + }; + + public NodePropertyResolver resolverSmallIcon = new NodePropertyResolver() { + public Object get(Node node) { + QNameNodeMap props = (QNameNodeMap)node.getProperties(); + String icon = (String)props.getRaw("app:icon"); + return (icon != null ? icon + "-16" : SPACE_SMALL_DEFAULT); + } + }; + + public NodePropertyResolver resolverMimetype = new NodePropertyResolver() { + public Object get(Node node) { + ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); + return (content != null ? content.getMimetype() : null); + } + }; + + public NodePropertyResolver resolverSize = new NodePropertyResolver() { + public Object get(Node node) { + ContentData content = (ContentData)node.getProperties().get(ContentModel.PROP_CONTENT); + return (content != null ? new Long(content.getSize()) : 0L); + } + }; + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action called from the Simple Search component. + * Sets up the SearchContext object with the values from the simple search menu. + */ + public void search(ActionEvent event) + { + // setup the search text string on the top-level navigation handler + UISimpleSearch search = (UISimpleSearch)event.getComponent(); + this.navigator.setSearchContext(search.getSearchContext()); + + navigateBrowseScreen(); + } + + /** + * Action called to Close the search dialog by returning to the last view node Id + */ + public void closeSearch(ActionEvent event) + { + // set the current node Id ready for page refresh + this.navigator.setCurrentNodeId( this.navigator.getCurrentNodeId() ); + } + + /** + * Update page size based on user selection + */ + public void updateSpacesPageSize(ActionEvent event) + { + try + { + int size = Integer.parseInt(this.pageSizeSpacesStr); + if (size >= 0) + { + this.pageSizeSpaces = size; + } + else + { + // reset to known value if this occurs + this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces); + } + } + catch (NumberFormatException err) + { + // reset to known value if this occurs + this.pageSizeSpacesStr = Integer.toString(this.pageSizeSpaces); + } + } + + /** + * Update page size based on user selection + */ + public void updateContentPageSize(ActionEvent event) + { + try + { + int size = Integer.parseInt(this.pageSizeContentStr); + if (size >= 0) + { + this.pageSizeContent = size; + } + else + { + // reset to known value if this occurs + this.pageSizeContentStr = Integer.toString(this.pageSizeContent); + } + } + catch (NumberFormatException err) + { + // reset to known value if this occurs + this.pageSizeContentStr = Integer.toString(this.pageSizeContent); + } + } + + /** + * Action called when a folder space is clicked. + * Navigate into the space. + */ + public void clickSpace(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + try + { + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + + // handle special folder link node case + if (ContentModel.TYPE_FOLDERLINK.equals(this.nodeService.getType(ref))) + { + ref = (NodeRef)this.nodeService.getProperty(ref, ContentModel.PROP_LINK_DESTINATION); + } + + clickSpace(ref); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + } + + /** + * Action called when a folder space is clicked. + * + * @param nodeRef The node being clicked + */ + public void clickSpace(NodeRef nodeRef) + { + // refresh UI based on node selection + updateUILocation(nodeRef); + } + + /** + * Handler called when a path element is clicked - navigate to the appropriate Space + */ + public void clickSpacePath(ActionEvent event) + { + UINodePath.PathElementEvent pathEvent = (UINodePath.PathElementEvent)event; + NodeRef ref = pathEvent.NodeReference; + try + { + // refresh UI based on node selection + this.updateUILocation(ref); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {ref.getId()}) ); + } + } + + /** + * Action called when a folders direct descendant (in the 'list' browse mode) is clicked. + * Navigate into the the descendant space. + */ + public void clickDescendantSpace(ActionEvent event) + { + UINodeDescendants.NodeSelectedEvent nodeEvent = (UINodeDescendants.NodeSelectedEvent)event; + NodeRef nodeRef = nodeEvent.NodeReference; + if (nodeRef == null) + { + throw new IllegalStateException("NodeRef returned from UINodeDescendants.NodeSelectedEvent cannot be null!"); + } + + if (logger.isDebugEnabled()) + logger.debug("Selected noderef Id: " + nodeRef.getId()); + + try + { + // user can either select a descendant of a node display on the page which means we + // must add the it's parent and itself to the breadcrumb + ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); + + if (logger.isDebugEnabled()) + { + logger.debug("Selected item getPrimaryParent().getChildRef() noderef Id: " + parentAssocRef.getChildRef().getId()); + logger.debug("Selected item getPrimaryParent().getParentRef() noderef Id: " + parentAssocRef.getParentRef().getId()); + logger.debug("Current value getNavigator().getCurrentNodeId() noderef Id: " + this.navigator.getCurrentNodeId()); + } + + if (nodeEvent.IsParent == false) + { + // a descendant of the displayed node was selected + // first refresh based on the parent and add to the breadcrumb + updateUILocation(parentAssocRef.getParentRef()); + + // now add our selected node + updateUILocation(nodeRef); + } + else + { + // else the parent ellipses i.e. the displayed node was selected + updateUILocation(nodeRef); + } + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {nodeRef.getId()}) ); + } + } + + /** + * Action event called by all Browse actions that need to setup a Space context + * before an action page/wizard is called. The context will be a Node in setActionSpace() which + * can be retrieved on the action page from BrowseBean.getActionSpace(). + * + * @param event ActionEvent + */ + public void setupSpaceAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + setupSpaceAction(id, true); + } + + /** + * Public helper to setup action pages with Space context + * + * @param id of the Space node to setup context for + */ + public void setupSpaceAction(String id, boolean invalidate) + { + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current space to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // resolve icon in-case one has not been set + node.addPropertyResolver("icon", this.resolverSpaceIcon); + + // prepare a node for the action context + setActionSpace(node); + + // setup the dispatch context in case it is required + this.navigator.setupDispatchContext(node); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + else + { + setActionSpace(null); + } + + // clear the UI state in preparation for finishing the next action + if (invalidate == true) + { + // use the context service to notify all registered beans + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + } + + /** + * Acrtion event called by Delete Space actions. We setup the action space as normal, then prepare + * any special case message string to be shown to the user if they are trying to delete specific spaces. + */ + public void setupDeleteAction(ActionEvent event) + { + String message = null; + + setupSpaceAction(event); + + Node node = getActionSpace(); + if (node != null) + { + NodeRef companyRootRef = new NodeRef(Repository.getStoreRef(), Application.getCompanyRootId()); + if (node.getNodeRef().equals(companyRootRef)) + { + message = Application.getMessage(FacesContext.getCurrentInstance(), MSG_DELETE_COMPANYROOT); + } + } + + setDeleteMessage(message); + } + + /** + * Action event called by all actions that need to setup a Content Document context on the + * BrowseBean before an action page/wizard is called. The context will be a Node in + * setDocument() which can be retrieved on the action page from BrowseBean.getDocument(). + */ + public void setupContentAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + setupContentAction(params.get("id"), true); + } + + /** + * Public helper to setup action pages with content context + * + * @param id of the content node to setup context for + */ + public void setupContentAction(String id, boolean invalidate) + { + if (id != null && id.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current document to: " + id); + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + Node node = new Node(ref); + + // store the URL to for downloading the content + if (ContentModel.TYPE_FILELINK.equals(node.getType())) + { + node.addPropertyResolver("url", this.resolverLinkDownload); + } + else + { + node.addPropertyResolver("url", this.resolverDownload); + } + node.addPropertyResolver("fileType32", this.resolverFileType32); + node.addPropertyResolver("mimetype", this.resolverMimetype); + node.addPropertyResolver("size", this.resolverSize); + + for (NodeEventListener listener : getNodeEventListeners()) + { + listener.created(node, node.getType()); + } + + // get hold of the DocumentDetailsBean and reset it + DocumentDetailsBean docDetails = (DocumentDetailsBean)FacesContext.getCurrentInstance(). + getExternalContext().getSessionMap().get("DocumentDetailsBean"); + if (docDetails != null) + { + docDetails.reset(); + } + + // remember the document + setDocument(node); + + // setup the dispatch context in case it is required + this.navigator.setupDispatchContext(node); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + } + else + { + setDocument(null); + } + + // clear the UI state in preparation for finishing the next action + if (invalidate == true) + { + // use the context service to notify all registered beans + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + } + + /** + * Removes the given node from the breadcrumb i.e. following a delete + * + * @param node The space to remove from the breadcrumb + */ + public void removeSpaceFromBreadcrumb(Node node) + { + List location = navigator.getLocation(); + IBreadcrumbHandler handler = location.get(location.size() - 1); + if (handler instanceof BrowseBreadcrumbHandler) + { + // see if the current breadcrumb location is our node + if ( ((BrowseBreadcrumbHandler)handler).getNodeRef().equals(node.getNodeRef()) == true ) + { + location.remove(location.size() - 1); + + // now work out which node to set the list to refresh against + if (location.size() != 0) + { + handler = location.get(location.size() - 1); + if (handler instanceof BrowseBreadcrumbHandler) + { + // change the current node Id + navigator.setCurrentNodeId(((BrowseBreadcrumbHandler)handler).getNodeRef().getId()); + } + else + { + // TODO: shouldn't do this - but for now the user home dir is the root! + navigator.setCurrentNodeId(Application.getCurrentUser(FacesContext.getCurrentInstance()).getHomeSpaceId()); + } + } + } + } + } + + /** + * Support for refresh of lists via special case for an External Access URL. + * these URLs restart the JSF lifecycle but an old UIRichList is restored from + * the component tree - which needs clearing "late" in the lifecycle process. + */ + public void externalAccessRefresh() + { + this.externalForceRefresh = true; + } + + /** + * Save the state of the panel that was expanded/collapsed + */ + public void expandPanel(ActionEvent event) + { + if (event instanceof ExpandedEvent) + { + String id = event.getComponent().getId(); + this.panels.put(id, ((ExpandedEvent)event).State); + } + } + + + // ------------------------------------------------------------------------------ + // Private helpers + + /** + * Initialise default values from client configuration + */ + private void initFromClientConfig() + { + ConfigService config = Application.getConfigService(FacesContext.getCurrentInstance()); + + this.viewsConfig = (ViewsConfigElement)config.getConfig("Views"). + getConfigElement(ViewsConfigElement.CONFIG_ELEMENT_ID); + + this.browseViewMode = this.viewsConfig.getDefaultView(PAGE_NAME_BROWSE); + int pageSize = this.viewsConfig.getDefaultPageSize(PAGE_NAME_BROWSE, this.browseViewMode); + setPageSizeContent(pageSize); + setPageSizeSpaces(pageSize); + } + + /** + * @return the Set of NodeEventListeners registered against this bean + */ + private Set getNodeEventListeners() + { + if (this.nodeEventListeners == null) + { + this.nodeEventListeners = new HashSet(); + + FacesContext fc = FacesContext.getCurrentInstance(); + + Config listenerConfig = Application.getConfigService(fc).getConfig("Node Event Listeners"); + if (listenerConfig != null) + { + ConfigElement listenerElement = listenerConfig.getConfigElement("node-event-listeners"); + if (listenerElement != null) + { + for (ConfigElement child : listenerElement.getChildren()) + { + if (child.getName().equals("listener")) + { + // retrieved the JSF Managed Bean identified in the config + String listenerName = child.getValue().trim(); + Object bean = FacesHelper.getManagedBean(fc, listenerName); + if (bean instanceof NodeEventListener) + { + addNodeEventListener((NodeEventListener)bean); + } + } + } + } + } + } + return this.nodeEventListeners; + } + + /** + * Refresh the UI after a Space selection change. Adds the selected space to the breadcrumb + * location path and also updates the list components in the UI. + * + * @param ref NodeRef of the selected space + */ + public void updateUILocation(NodeRef ref) + { + // get the current breadcrumb location and append a new handler to it + // our handler know the ID of the selected node and the display label for it + List location = this.navigator.getLocation(); + if (location.size() != 0) + { + // attempt to find the ID - if it's already in the breadcrumb then we + // navigate directly to that node - rather than add duplication to the breadcrumb path + boolean foundNode = false; + for (int i=0; i newLocation = new ArrayList(i+1); + //newLocation.addAll(location.subList(0, i + 1)); + //this.navigator.setLocation(newLocation); + // TODO: but instead for now we do this: + int count = location.size(); + for (int n=i+1; n nodeEventListeners = null; + + /** Collapsable Panel state */ + private Map panels = new HashMap(4, 1.0f); + + /** Component references */ + protected UIRichList spacesRichList; + protected UIRichList contentRichList; + private UIStatusMessage statusMessage; + + /** Transient lists of container and content nodes for display */ + protected List containerNodes = null; + protected List contentNodes = null; + + /** The current space and it's properties - if any */ + protected Node actionSpace; + + /** The current document */ + protected Node document; + + /** Special message to display when user deleting certain folders e.g. Company Home */ + private String deleteMessage; + + /** The current browse view mode - set to a well known IRichListRenderer identifier */ + private String browseViewMode; + + /** The current browse view page sizes */ + private int pageSizeSpaces; + private int pageSizeContent; + private String pageSizeSpacesStr; + private String pageSizeContentStr; + + /** True if current space has a dashboard (template) view available */ + private boolean dashboardView; + + private boolean externalForceRefresh = false; +} diff --git a/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java b/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java index ba88d54c5e..9a90fb796a 100644 --- a/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java +++ b/source/java/org/alfresco/web/bean/CheckinCheckoutBean.java @@ -1,981 +1,1050 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.web.bean; - -import java.io.File; -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.Map; - -import javax.faces.context.FacesContext; -import javax.faces.event.ActionEvent; -import javax.transaction.UserTransaction; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.web.app.AlfrescoNavigationHandler; -import org.alfresco.web.app.Application; -import org.alfresco.web.app.context.UIContextService; -import org.alfresco.web.app.servlet.DownloadContentServlet; -import org.alfresco.web.bean.repository.Node; -import org.alfresco.web.bean.repository.Repository; -import org.alfresco.web.ui.common.Utils; -import org.alfresco.web.ui.common.component.UIActionLink; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * @author Kevin Roast - */ -public class CheckinCheckoutBean -{ - // ------------------------------------------------------------------------------ - // Bean property getters and setters - - /** - * @param navigator The NavigationBean to set. - */ - public void setNavigator(NavigationBean navigator) - { - this.navigator = navigator; - } - - /** - * @return Returns the BrowseBean. - */ - public BrowseBean getBrowseBean() - { - return this.browseBean; - } - - /** - * @param browseBean The BrowseBean to set. - */ - public void setBrowseBean(BrowseBean browseBean) - { - this.browseBean = browseBean; - } - - /** - * @return Returns the NodeService. - */ - public NodeService getNodeService() - { - return this.nodeService; - } - - /** - * @param nodeService The NodeService to set. - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @return Returns the VersionOperationsService. - */ - public CheckOutCheckInService getVersionOperationsService() - { - return this.versionOperationsService; - } - - /** - * @param versionOperationsService The VersionOperationsService to set. - */ - public void setVersionOperationsService(CheckOutCheckInService versionOperationsService) - { - this.versionOperationsService = versionOperationsService; - } - - /** - * @return Returns the ContentService. - */ - public ContentService getContentService() - { - return this.contentService; - } - - /** - * @param contentService The ContentService to set. - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @return The document node being used for the current operation - */ - public Node getDocument() - { - return this.document; - } - - /** - * @param document The document node to be used for the current operation - */ - public void setDocument(Node document) - { - this.document = document; - } - - /** - * @return Returns the working copy Document. - */ - public Node getWorkingDocument() - { - return this.workingDocument; - } - - /** - * @param workingDocument The working copy Document to set. - */ - public void setWorkingDocument(Node workingDocument) - { - this.workingDocument = workingDocument; - } - - /** - * Determines whether the document being checked in has - * the versionable aspect applied - * - * @return true if the versionable aspect is applied - */ - public boolean isVersionable() - { - return getDocument().hasAspect(ContentModel.ASPECT_VERSIONABLE); - } - - /** - * @param keepCheckedOut The keepCheckedOut to set. - */ - public void setKeepCheckedOut(boolean keepCheckedOut) - { - this.keepCheckedOut = keepCheckedOut; - } - - /** - * @return Returns the keepCheckedOut. - */ - public boolean getKeepCheckedOut() - { - return this.keepCheckedOut; - } - - /** - * @param minorChange The minorChange to set. - */ - public void setMinorChange(boolean minorChange) - { - this.minorChange = minorChange; - } - - /** - * @return Returns the minorChange flag. - */ - public boolean getMinorChange() - { - return this.minorChange; - } - - /** - * @return Returns the version history notes. - */ - public String getVersionNotes() - { - return this.versionNotes; - } - - /** - * @param versionNotes The version history notes to set. - */ - public void setVersionNotes(String versionNotes) - { - this.versionNotes = versionNotes; - } - - /** - * @return Returns the selected Space Id. - */ - public NodeRef getSelectedSpaceId() - { - return this.selectedSpaceId; - } - - /** - * @param selectedSpaceId The selected Space Id to set. - */ - public void setSelectedSpaceId(NodeRef selectedSpaceId) - { - this.selectedSpaceId = selectedSpaceId; - } - - /** - * @return Returns the copy location. Either the current or other space. - */ - public String getCopyLocation() - { - if (this.fileName != null) - { - return CheckinCheckoutBean.COPYLOCATION_OTHER; - } - else - { - return this.copyLocation; - } - } - - /** - * @param copyLocation The copy location. Either the current or other space. - */ - public void setCopyLocation(String copyLocation) - { - this.copyLocation = copyLocation; - } - - /** - * @return Returns the message to display when a file has been uploaded - */ - public String getFileUploadSuccessMsg() - { - String msg = Application.getMessage(FacesContext.getCurrentInstance(), "file_upload_success"); - return MessageFormat.format(msg, new Object[] {getFileName()}); - } - - /** - * @return Returns the name of the file - */ - public String getFileName() - { - // try and retrieve the file and filename from the file upload bean - // representing the file we previously uploaded. - FacesContext ctx = FacesContext.getCurrentInstance(); - FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). - get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); - if (fileBean != null) - { - this.file = fileBean.getFile(); - this.fileName = fileBean.getFileName(); - } - - return this.fileName; - } - - /** - * @param fileName The name of the file - */ - public void setFileName(String fileName) - { - this.fileName = fileName; - - // we also need to keep the file upload bean in sync - FacesContext ctx = FacesContext.getCurrentInstance(); - FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). - get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); - if (fileBean != null) - { - fileBean.setFileName(this.fileName); - } - } - - /** - * @return Returns the document content used for HTML in-line editing. - */ - public String getDocumentContent() - { - return this.documentContent; - } - - /** - * @param documentContent The document content for HTML in-line editing. - */ - public void setDocumentContent(String documentContent) - { - this.documentContent = documentContent; - } - - /** - * @return Returns output from the in-line editor page. - */ - public String getEditorOutput() - { - return this.editorOutput; - } - - /** - * @param editorOutput The output from the in-line editor page - */ - public void setEditorOutput(String editorOutput) - { - this.editorOutput = editorOutput; - } - - - // ------------------------------------------------------------------------------ - // Navigation action event handlers - - /** - * Action event called by all actions that need to setup a Content Document context on the - * CheckinCheckoutBean before an action page/wizard is called. The context will be a Node in - * setDocument() which can be retrieved on action pages via getDocument(). - * - * @param event ActionEvent - */ - public void setupContentAction(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - if (id != null && id.length() != 0) - { - setupContentDocument(id); - } - else - { - setDocument(null); - } - - clearUpload(); - } - - /** - * Setup a content document node context - * - * @param id GUID of the node to setup as the content document context - * - * @return The Node - */ - private Node setupContentDocument(String id) - { - if (logger.isDebugEnabled()) - logger.debug("Setup for action, setting current document to: " + id); - - Node node = null; - - try - { - // create the node ref, then our node representation - NodeRef ref = new NodeRef(Repository.getStoreRef(), id); - node = new Node(ref); - - // create content URL to the content download servlet with ID and expected filename - // the myfile part will be ignored by the servlet but gives the browser a hint - String url = DownloadContentServlet.generateDownloadURL(ref, node.getName()); - node.getProperties().put("url", url); - node.getProperties().put("workingCopy", node.hasAspect(ContentModel.ASPECT_WORKING_COPY)); - node.getProperties().put("fileType32", Utils.getFileTypeImage(node.getName(), false)); - - // remember the document - setDocument(node); - - // refresh the UI, calling this method now is fine as it basically makes sure certain - // beans clear the state - so when we finish here other beans will have been reset - UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); - } - catch (InvalidNodeRefException refErr) - { - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); - } - - return node; - } - - /** - * Action called upon completion of the Check Out file page - */ - public String checkoutFile() - { - String outcome = null; - - UserTransaction tx = null; - - Node node = getDocument(); - if (node != null) - { - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); - tx.begin(); - - if (logger.isDebugEnabled()) - logger.debug("Trying to checkout content node Id: " + node.getId()); - - // checkout the node content to create a working copy - if (logger.isDebugEnabled()) - { - logger.debug("Checkout copy location: " + getCopyLocation()); - logger.debug("Selected Space Id: " + this.selectedSpaceId); - } - NodeRef workingCopyRef; - if (getCopyLocation().equals(COPYLOCATION_OTHER) && this.selectedSpaceId != null) - { - // checkout to a arbituary parent Space - NodeRef destRef = this.selectedSpaceId; - - ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destRef); - workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef(), - destRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); - } - else - { - workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef()); - } - - // set the working copy Node instance - Node workingCopy = new Node(workingCopyRef); - setWorkingDocument(workingCopy); - - // create content URL to the content download servlet with ID and expected filename - // the myfile part will be ignored by the servlet but gives the browser a hint - String url = DownloadContentServlet.generateDownloadURL(workingCopyRef, workingCopy.getName()); - - workingCopy.getProperties().put("url", url); - workingCopy.getProperties().put("fileType32", Utils.getFileTypeImage(workingCopy.getName(), false)); - - // commit the transaction - tx.commit(); - - // show the page that display the checkout link - outcome = "checkoutFileLink"; - } - catch (Throwable err) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - Utils.addErrorMessage(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_CHECKOUT) + err.getMessage(), err); - } - } - else - { - logger.warn("WARNING: checkoutFile called without a current Document!"); - } - - return outcome; - } - - /** - * Action called upon completion of the Check Out file Link download page - */ - public String checkoutFileOK() - { - String outcome = null; - - Node node = getWorkingDocument(); - if (node != null) - { - // reset the underlying node - if (this.browseBean.getDocument() != null) - { - this.browseBean.getDocument().reset(); - } - - // clean up and clear action context - clearUpload(); - setDocument(null); - setWorkingDocument(null); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - else - { - logger.warn("WARNING: checkoutFileOK called without a current WorkingDocument!"); - } - - return outcome; - } - - /** - * Action called upon completion of the Edit File download page - */ - public String editFileOK() - { - String outcome = null; - - Node node = getDocument(); - if (node != null) - { - // clean up and clear action context - clearUpload(); - setDocument(null); - setWorkingDocument(null); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - else - { - logger.warn("WARNING: editFileOK called without a current Document!"); - } - - return outcome; - } - - /** - * Action handler called to calculate which editing screen to display based on the mimetype - * of a document. If appropriate, the in-line editing screen will be shown. - */ - public void editFile(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - if (id != null && id.length() != 0) - { - boolean editingInline = false; - Node node = setupContentDocument(id); - - // detect the inline editing aspect to see which edit mode to use - if (node.hasAspect(ContentModel.ASPECT_INLINEEDITABLE) && - node.getProperties().get(ContentModel.PROP_EDITINLINE) != null && - ((Boolean)node.getProperties().get(ContentModel.PROP_EDITINLINE)).booleanValue() == true) - { - // retrieve the content reader for this node - ContentReader reader = getContentService().getReader(node.getNodeRef(), ContentModel.PROP_CONTENT); - if (reader != null) - { - editingInline = true; - String mimetype = reader.getMimetype(); - - // calculate which editor screen to display - if (MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(mimetype) || - MimetypeMap.MIMETYPE_XML.equals(mimetype) || - MimetypeMap.MIMETYPE_TEXT_CSS.equals(mimetype) || - MimetypeMap.MIMETYPE_JAVASCRIPT.equals(mimetype)) - { - // make content available to the editing screen - setEditorOutput(reader.getContentString()); - - // navigate to appropriate screen - FacesContext fc = FacesContext.getCurrentInstance(); - this.navigator.setupDispatchContext(node); - String outcome; - if (MimetypeMap.MIMETYPE_XML.equals(mimetype)) - { - outcome = "dialog:editXmlInline"; - } - else - { - outcome = "dialog:editTextInline"; - } - fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome); - } - else - { - // make content available to the editing screen - setDocumentContent(reader.getContentString()); - setEditorOutput(null); - - // navigate to appropriate screen - FacesContext fc = FacesContext.getCurrentInstance(); - this.navigator.setupDispatchContext(node); - fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:editHtmlInline"); - } - } - } - - if (editingInline == false) - { - // normal downloadable document - FacesContext fc = FacesContext.getCurrentInstance(); - this.navigator.setupDispatchContext(node); - fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:editFile"); - } - } - } - - /** - * Action handler called to set the content of a node from an inline editing page. - */ - public String editInlineOK() - { - String outcome = null; - - UserTransaction tx = null; - - Node node = getDocument(); - if (node != null) - { - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); - tx.begin(); - - if (logger.isDebugEnabled()) - logger.debug("Trying to update content node Id: " + node.getId()); - - // get an updating writer that we can use to modify the content on the current node - ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); - writer.putContent(this.editorOutput); - - // commit the transaction - tx.commit(); - - // clean up and clear action context - clearUpload(); - setDocument(null); - setDocumentContent(null); - setEditorOutput(null); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - catch (Throwable err) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - Utils.addErrorMessage(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage()); - } - } - else - { - logger.warn("WARNING: editInlineOK called without a current Document!"); - } - - return outcome; - } - - /** - * Action to undo the checkout of a document just checked out from the checkout screen. - */ - public String undoCheckout() - { - String outcome = null; - - Node node = getWorkingDocument(); - if (node != null) - { - try - { - // try to cancel checkout of the working copy - this.versionOperationsService.cancelCheckout(node.getNodeRef()); - - clearUpload(); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - catch (Throwable err) - { - Utils.addErrorMessage(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_CANCELCHECKOUT) + err.getMessage(), err); - } - } - else - { - logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); - } - - return outcome; - } - - /** - * Action to undo the checkout of a locked document. This document may either by the original copy - * or the working copy node. Therefore calculate which it is, if the working copy is found then - * we simply cancel checkout on that document. If the original copy is found then we need to find - * the appropriate working copy and perform the action on that node. - */ - public String undoCheckoutFile() - { - String outcome = null; - - Node node = getDocument(); - if (node != null) - { - try - { - if (node.hasAspect(ContentModel.ASPECT_WORKING_COPY)) - { - this.versionOperationsService.cancelCheckout(node.getNodeRef()); - } - else if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) - { - // TODO: find the working copy for this document and cancel the checkout on it - // is this possible? as currently only the workingcopy aspect has the copyReference - // attribute - this means we cannot find out where the copy is to cancel it! - // can we construct an XPath node lookup? - throw new RuntimeException("NOT IMPLEMENTED"); - } - else - { - throw new IllegalStateException("Node supplied for undo checkout has neither Working Copy or Locked aspect!"); - } - - clearUpload(); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse"; - } - catch (Throwable err) - { - Utils.addErrorMessage(MSG_ERROR_CANCELCHECKOUT + err.getMessage(), err); - } - } - else - { - logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); - } - - return outcome; - } - - /** - * Action called upon completion of the Check In file page - */ - public String checkinFileOK() - { - String outcome = null; - - UserTransaction tx = null; - - // NOTE: for checkin the document node _is_ the working document! - Node node = getDocument(); - if (node != null && (getCopyLocation().equals(COPYLOCATION_CURRENT) || this.getFileName() != null)) - { - try - { - FacesContext context = FacesContext.getCurrentInstance(); - tx = Repository.getUserTransaction(context); - tx.begin(); - - if (logger.isDebugEnabled()) - logger.debug("Trying to checkin content node Id: " + node.getId()); - - // we can either checkin the content from the current working copy node - // which would have been previously updated by the user - String contentUrl; - if (getCopyLocation().equals(COPYLOCATION_CURRENT)) - { - ContentData contentData = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT); - contentUrl = (contentData == null ? null : contentData.getContentUrl()); - } - // or specify a specific file as the content instead - else - { - // add the content to an anonymous but permanent writer location - // we can then retrieve the URL to the content to to be set on the node during checkin - ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); - // also update the mime type in case a different type of file is uploaded - String mimeType = Repository.getMimeTypeForFileName(context, this.fileName); - writer.setMimetype(mimeType); - writer.putContent(this.file); - contentUrl = writer.getContentUrl(); - } - - if (contentUrl == null || contentUrl.length() == 0) - { - throw new IllegalStateException("Content URL is empty for specified working copy content node!"); - } - - // add version history text to props - Map props = new HashMap(1, 1.0f); - props.put(Version.PROP_DESCRIPTION, this.versionNotes); - // set the flag for minor or major change - if (this.minorChange) - { - props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); - } - else - { - props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); - } - - // perform the checkin - this.versionOperationsService.checkin(node.getNodeRef(), - props, contentUrl, this.keepCheckedOut); - - // commit the transaction - tx.commit(); - - // clear action context - setDocument(null); - clearUpload(); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME + - AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse"; - } - catch (Throwable err) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - Utils.addErrorMessage(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_CHECKIN) + err.getMessage(), err); - } - } - else - { - logger.warn("WARNING: checkinFileOK called without a current Document!"); - } - - return outcome; - } - - /** - * Action called upon completion of the Update File page - */ - public String updateFileOK() - { - String outcome = null; - - UserTransaction tx = null; - - // NOTE: for update the document node _is_ the working document! - Node node = getDocument(); - if (node != null && this.getFileName() != null) - { - try - { - FacesContext context = FacesContext.getCurrentInstance(); - tx = Repository.getUserTransaction(context); - tx.begin(); - - if (logger.isDebugEnabled()) - logger.debug("Trying to update content node Id: " + node.getId()); - - // get an updating writer that we can use to modify the content on the current node - ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); - - // also update the mime type in case a different type of file is uploaded - String mimeType = Repository.getMimeTypeForFileName(context, this.fileName); - writer.setMimetype(mimeType); - - writer.putContent(this.file); - - // commit the transaction - tx.commit(); - - // clear action context - setDocument(null); - clearUpload(); - - outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - catch (Throwable err) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - Utils.addErrorMessage(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage(), err); - } - } - else - { - logger.warn("WARNING: updateFileOK called without a current Document!"); - } - - return outcome; - } - - /** - * Deals with the cancel button being pressed on the check in file page - */ - public String cancel() - { - // reset the state - clearUpload(); - - return AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; - } - - /** - * Clear form state and upload file bean - */ - private void clearUpload() - { - // delete the temporary file we uploaded earlier - if (this.file != null) - { - this.file.delete(); - } - - this.file = null; - this.fileName = null; - this.keepCheckedOut = false; - this.minorChange = true; - this.copyLocation = COPYLOCATION_CURRENT; - this.versionNotes = ""; - this.selectedSpaceId = null; - - // remove the file upload bean from the session - FacesContext ctx = FacesContext.getCurrentInstance(); - ctx.getExternalContext().getSessionMap().remove(FileUploadBean.FILE_UPLOAD_BEAN_NAME); - } - - - // ------------------------------------------------------------------------------ - // Private data - - private static Log logger = LogFactory.getLog(CheckinCheckoutBean.class); - - /** I18N messages */ - public static final String MSG_ERROR_CHECKIN = "error_checkin"; - public static final String MSG_ERROR_CANCELCHECKOUT = "error_cancel_checkout"; - public static final String MSG_ERROR_UPDATE = "error_update"; - public static final String MSG_ERROR_CHECKOUT = "error_checkout"; - - /** constants for copy location selection */ - private static final String COPYLOCATION_CURRENT = "current"; - private static final String COPYLOCATION_OTHER = "other"; - - /** The current document */ - private Node document; - - /** The working copy of the document we are checking out */ - private Node workingDocument; - - /** Content of the document used for HTML in-line editing */ - private String documentContent; - - /** Content of the document returned from in-line editing */ - private String editorOutput; - - /** transient form and upload properties */ - private File file; - private String fileName; - private boolean keepCheckedOut = false; - private boolean minorChange = true; - private String copyLocation = COPYLOCATION_CURRENT; - private String versionNotes = ""; - private NodeRef selectedSpaceId = null; - - /** The BrowseBean to be used by the bean */ - protected BrowseBean browseBean; - - /** The NavigationBean bean reference */ - protected NavigationBean navigator; - - /** The NodeService to be used by the bean */ - protected NodeService nodeService; - - /** The VersionOperationsService to be used by the bean */ - protected CheckOutCheckInService versionOperationsService; - - /** The ContentService to be used by the bean */ - protected ContentService contentService; -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.bean; + +import java.io.File; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import javax.faces.context.FacesContext; +import javax.faces.event.ActionEvent; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.AlfrescoNavigationHandler; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.context.UIContextService; +import org.alfresco.web.app.servlet.DownloadContentServlet; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.ui.common.Utils; +import org.alfresco.web.ui.common.component.UIActionLink; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Kevin Roast + */ +public class CheckinCheckoutBean +{ + // ------------------------------------------------------------------------------ + // Bean property getters and setters + + /** + * @param navigator The NavigationBean to set. + */ + public void setNavigator(NavigationBean navigator) + { + this.navigator = navigator; + } + + /** + * @return Returns the BrowseBean. + */ + public BrowseBean getBrowseBean() + { + return this.browseBean; + } + + /** + * @param browseBean The BrowseBean to set. + */ + public void setBrowseBean(BrowseBean browseBean) + { + this.browseBean = browseBean; + } + + /** + * @return Returns the NodeService. + */ + public NodeService getNodeService() + { + return this.nodeService; + } + + /** + * @param nodeService The NodeService to set. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @return Returns the VersionOperationsService. + */ + public CheckOutCheckInService getVersionOperationsService() + { + return this.versionOperationsService; + } + + /** + * @param versionOperationsService The VersionOperationsService to set. + */ + public void setVersionOperationsService(CheckOutCheckInService versionOperationsService) + { + this.versionOperationsService = versionOperationsService; + } + + /** + * @return Returns the ContentService. + */ + public ContentService getContentService() + { + return this.contentService; + } + + /** + * @param contentService The ContentService to set. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param workflowService The WorkflowService to set. + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /** + * @return The document node being used for the current operation + */ + public Node getDocument() + { + return this.document; + } + + /** + * @param document The document node to be used for the current operation + */ + public void setDocument(Node document) + { + this.document = document; + } + + /** + * @return Returns the working copy Document. + */ + public Node getWorkingDocument() + { + return this.workingDocument; + } + + /** + * @param workingDocument The working copy Document to set. + */ + public void setWorkingDocument(Node workingDocument) + { + this.workingDocument = workingDocument; + } + + /** + * Determines whether the document being checked in has + * the versionable aspect applied + * + * @return true if the versionable aspect is applied + */ + public boolean isVersionable() + { + return getDocument().hasAspect(ContentModel.ASPECT_VERSIONABLE); + } + + /** + * @param keepCheckedOut The keepCheckedOut to set. + */ + public void setKeepCheckedOut(boolean keepCheckedOut) + { + this.keepCheckedOut = keepCheckedOut; + } + + /** + * @return Returns the keepCheckedOut. + */ + public boolean getKeepCheckedOut() + { + return this.keepCheckedOut; + } + + /** + * @param minorChange The minorChange to set. + */ + public void setMinorChange(boolean minorChange) + { + this.minorChange = minorChange; + } + + /** + * @return Returns the minorChange flag. + */ + public boolean getMinorChange() + { + return this.minorChange; + } + + /** + * @return Returns the version history notes. + */ + public String getVersionNotes() + { + return this.versionNotes; + } + + /** + * @param versionNotes The version history notes to set. + */ + public void setVersionNotes(String versionNotes) + { + this.versionNotes = versionNotes; + } + + /** + * @return Returns the selected Space Id. + */ + public NodeRef getSelectedSpaceId() + { + return this.selectedSpaceId; + } + + /** + * @param selectedSpaceId The selected Space Id to set. + */ + public void setSelectedSpaceId(NodeRef selectedSpaceId) + { + this.selectedSpaceId = selectedSpaceId; + } + + /** + * @return Returns the copy location. Either the current or other space. + */ + public String getCopyLocation() + { + if (this.fileName != null) + { + return CheckinCheckoutBean.COPYLOCATION_OTHER; + } + else + { + return this.copyLocation; + } + } + + /** + * @param copyLocation The copy location. Either the current or other space. + */ + public void setCopyLocation(String copyLocation) + { + this.copyLocation = copyLocation; + } + + /** + * @return Returns the message to display when a file has been uploaded + */ + public String getFileUploadSuccessMsg() + { + String msg = Application.getMessage(FacesContext.getCurrentInstance(), "file_upload_success"); + return MessageFormat.format(msg, new Object[] {getFileName()}); + } + + /** + * @return Returns the name of the file + */ + public String getFileName() + { + // try and retrieve the file and filename from the file upload bean + // representing the file we previously uploaded. + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + this.file = fileBean.getFile(); + this.fileName = fileBean.getFileName(); + } + + return this.fileName; + } + + /** + * @param fileName The name of the file + */ + public void setFileName(String fileName) + { + this.fileName = fileName; + + // we also need to keep the file upload bean in sync + FacesContext ctx = FacesContext.getCurrentInstance(); + FileUploadBean fileBean = (FileUploadBean)ctx.getExternalContext().getSessionMap(). + get(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + if (fileBean != null) + { + fileBean.setFileName(this.fileName); + } + } + + /** + * @return Returns the document content used for HTML in-line editing. + */ + public String getDocumentContent() + { + return this.documentContent; + } + + /** + * @param documentContent The document content for HTML in-line editing. + */ + public void setDocumentContent(String documentContent) + { + this.documentContent = documentContent; + } + + /** + * @return Returns output from the in-line editor page. + */ + public String getEditorOutput() + { + return this.editorOutput; + } + + /** + * @param editorOutput The output from the in-line editor page + */ + public void setEditorOutput(String editorOutput) + { + this.editorOutput = editorOutput; + } + + + // ------------------------------------------------------------------------------ + // Navigation action event handlers + + /** + * Action event called by all actions that need to setup a Content Document context on the + * CheckinCheckoutBean before an action page/wizard is called. The context will be a Node in + * setDocument() which can be retrieved on action pages via getDocument(). + * + * @param event ActionEvent + */ + public void setupContentAction(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + setupContentDocument(id); + } + else + { + setDocument(null); + } + + resetState(); + } + + public void setupWorkflowContentAction(ActionEvent event) + { + // do the common processing + setupContentAction(event); + + // retrieve the id of the task + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + this.workflowTaskId = params.get("taskId"); + + this.isWorkflowAction = true; + + if (logger.isDebugEnabled()) + logger.debug("Setup for workflow package action for task id: " + this.workflowTaskId); + } + + /** + * Setup a content document node context + * + * @param id GUID of the node to setup as the content document context + * + * @return The Node + */ + private Node setupContentDocument(String id) + { + if (logger.isDebugEnabled()) + logger.debug("Setup for action, setting current document to: " + id); + + Node node = null; + + try + { + // create the node ref, then our node representation + NodeRef ref = new NodeRef(Repository.getStoreRef(), id); + node = new Node(ref); + + // create content URL to the content download servlet with ID and expected filename + // the myfile part will be ignored by the servlet but gives the browser a hint + String url = DownloadContentServlet.generateDownloadURL(ref, node.getName()); + node.getProperties().put("url", url); + node.getProperties().put("workingCopy", node.hasAspect(ContentModel.ASPECT_WORKING_COPY)); + node.getProperties().put("fileType32", Utils.getFileTypeImage(node.getName(), false)); + + // remember the document + setDocument(node); + + // refresh the UI, calling this method now is fine as it basically makes sure certain + // beans clear the state - so when we finish here other beans will have been reset + UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); + } + catch (InvalidNodeRefException refErr) + { + Utils.addErrorMessage(MessageFormat.format(Application.getMessage( + FacesContext.getCurrentInstance(), Repository.ERROR_NODEREF), new Object[] {id}) ); + } + + return node; + } + + /** + * Action called upon completion of the Check Out file page + */ + public String checkoutFile() + { + String outcome = null; + + UserTransaction tx = null; + + Node node = getDocument(); + if (node != null) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to checkout content node Id: " + node.getId()); + + // checkout the node content to create a working copy + if (logger.isDebugEnabled()) + { + logger.debug("Checkout copy location: " + getCopyLocation()); + logger.debug("Selected Space Id: " + this.selectedSpaceId); + } + NodeRef workingCopyRef = null; + if (getCopyLocation().equals(COPYLOCATION_OTHER) && this.selectedSpaceId != null) + { + // checkout to a arbituary parent Space + NodeRef destRef = this.selectedSpaceId; + + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destRef); + workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef(), + destRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); + } + else + { + // checkout the content to the current space + workingCopyRef = this.versionOperationsService.checkout(node.getNodeRef()); + + // if this is a workflow action and there is a task id present we need + // to also link the working copy to the workflow package so it appears + // in the resources panel in the manage task dialog + if (this.isWorkflowAction && this.workflowTaskId != null) + { + WorkflowTask task = this.workflowService.getTaskById(this.workflowTaskId); + if (task != null) + { + NodeRef workflowPackage = (NodeRef)task.properties.get(WorkflowModel.ASSOC_PACKAGE); + if (workflowPackage != null) + { + this.nodeService.addChild(workflowPackage, workingCopyRef, + ContentModel.ASSOC_CONTAINS, QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName((String)this.nodeService.getProperty( + workingCopyRef, ContentModel.PROP_NAME)))); + + if (logger.isDebugEnabled()) + logger.debug("Added working copy to workflow package: " + workflowPackage); + } + } + } + } + + // set the working copy Node instance + Node workingCopy = new Node(workingCopyRef); + setWorkingDocument(workingCopy); + + // create content URL to the content download servlet with ID and expected filename + // the myfile part will be ignored by the servlet but gives the browser a hint + String url = DownloadContentServlet.generateDownloadURL(workingCopyRef, workingCopy.getName()); + + workingCopy.getProperties().put("url", url); + workingCopy.getProperties().put("fileType32", Utils.getFileTypeImage(workingCopy.getName(), false)); + + // commit the transaction + tx.commit(); + + // show the page that display the checkout link + outcome = "checkoutFileLink"; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CHECKOUT) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: checkoutFile called without a current Document!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Check Out file Link download page + */ + public String checkoutFileOK() + { + String outcome = null; + + Node node = getWorkingDocument(); + if (node != null) + { + // reset the underlying node + if (this.browseBean.getDocument() != null) + { + this.browseBean.getDocument().reset(); + } + + // clean up and clear action context + resetState(); + setDocument(null); + setWorkingDocument(null); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + else + { + logger.warn("WARNING: checkoutFileOK called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Edit File download page + */ + public String editFileOK() + { + String outcome = null; + + Node node = getDocument(); + if (node != null) + { + // clean up and clear action context + resetState(); + setDocument(null); + setWorkingDocument(null); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + else + { + logger.warn("WARNING: editFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action handler called to calculate which editing screen to display based on the mimetype + * of a document. If appropriate, the in-line editing screen will be shown. + */ + public void editFile(ActionEvent event) + { + UIActionLink link = (UIActionLink)event.getComponent(); + Map params = link.getParameterMap(); + String id = params.get("id"); + if (id != null && id.length() != 0) + { + boolean editingInline = false; + Node node = setupContentDocument(id); + + // detect the inline editing aspect to see which edit mode to use + if (node.hasAspect(ContentModel.ASPECT_INLINEEDITABLE) && + node.getProperties().get(ContentModel.PROP_EDITINLINE) != null && + ((Boolean)node.getProperties().get(ContentModel.PROP_EDITINLINE)).booleanValue() == true) + { + // retrieve the content reader for this node + ContentReader reader = getContentService().getReader(node.getNodeRef(), ContentModel.PROP_CONTENT); + if (reader != null) + { + editingInline = true; + String mimetype = reader.getMimetype(); + + // calculate which editor screen to display + if (MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(mimetype) || + MimetypeMap.MIMETYPE_XML.equals(mimetype) || + MimetypeMap.MIMETYPE_TEXT_CSS.equals(mimetype) || + MimetypeMap.MIMETYPE_JAVASCRIPT.equals(mimetype)) + { + // make content available to the editing screen + setEditorOutput(reader.getContentString()); + + // navigate to appropriate screen + FacesContext fc = FacesContext.getCurrentInstance(); + this.navigator.setupDispatchContext(node); + String outcome; + if (MimetypeMap.MIMETYPE_XML.equals(mimetype)) + { + outcome = "dialog:editXmlInline"; + } + else + { + outcome = "dialog:editTextInline"; + } + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, outcome); + } + else + { + // make content available to the editing screen + setDocumentContent(reader.getContentString()); + setEditorOutput(null); + + // navigate to appropriate screen + FacesContext fc = FacesContext.getCurrentInstance(); + this.navigator.setupDispatchContext(node); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:editHtmlInline"); + } + } + } + + if (editingInline == false) + { + // normal downloadable document + FacesContext fc = FacesContext.getCurrentInstance(); + this.navigator.setupDispatchContext(node); + fc.getApplication().getNavigationHandler().handleNavigation(fc, null, "dialog:editFile"); + } + } + } + + /** + * Action handler called to set the content of a node from an inline editing page. + */ + public String editInlineOK() + { + String outcome = null; + + UserTransaction tx = null; + + Node node = getDocument(); + if (node != null) + { + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to update content node Id: " + node.getId()); + + // get an updating writer that we can use to modify the content on the current node + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.putContent(this.editorOutput); + + // commit the transaction + tx.commit(); + + // clean up and clear action context + resetState(); + setDocument(null); + setDocumentContent(null); + setEditorOutput(null); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage()); + } + } + else + { + logger.warn("WARNING: editInlineOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action to undo the checkout of a document just checked out from the checkout screen. + */ + public String undoCheckout() + { + String outcome = null; + + Node node = getWorkingDocument(); + if (node != null) + { + try + { + // try to cancel checkout of the working copy + this.versionOperationsService.cancelCheckout(node.getNodeRef()); + + resetState(); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + catch (Throwable err) + { + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CANCELCHECKOUT) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action to undo the checkout of a locked document. This document may either by the original copy + * or the working copy node. Therefore calculate which it is, if the working copy is found then + * we simply cancel checkout on that document. If the original copy is found then we need to find + * the appropriate working copy and perform the action on that node. + */ + public String undoCheckoutFile() + { + String outcome = null; + + Node node = getDocument(); + if (node != null) + { + try + { + if (node.hasAspect(ContentModel.ASPECT_WORKING_COPY)) + { + this.versionOperationsService.cancelCheckout(node.getNodeRef()); + } + else if (node.hasAspect(ContentModel.ASPECT_LOCKABLE)) + { + // TODO: find the working copy for this document and cancel the checkout on it + // is this possible? as currently only the workingcopy aspect has the copyReference + // attribute - this means we cannot find out where the copy is to cancel it! + // can we construct an XPath node lookup? + throw new RuntimeException("NOT IMPLEMENTED"); + } + else + { + throw new IllegalStateException("Node supplied for undo checkout has neither Working Copy or Locked aspect!"); + } + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + + if (this.isWorkflowAction == false) + { + outcome = outcome + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse"; + } + + resetState(); + } + catch (Throwable err) + { + Utils.addErrorMessage(MSG_ERROR_CANCELCHECKOUT + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: undoCheckout called without a current WorkingDocument!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Check In file page + */ + public String checkinFileOK() + { + String outcome = null; + + UserTransaction tx = null; + + // NOTE: for checkin the document node _is_ the working document! + Node node = getDocument(); + if (node != null && (getCopyLocation().equals(COPYLOCATION_CURRENT) || this.getFileName() != null)) + { + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to checkin content node Id: " + node.getId()); + + // we can either checkin the content from the current working copy node + // which would have been previously updated by the user + String contentUrl; + if (getCopyLocation().equals(COPYLOCATION_CURRENT)) + { + ContentData contentData = (ContentData) node.getProperties().get(ContentModel.PROP_CONTENT); + contentUrl = (contentData == null ? null : contentData.getContentUrl()); + } + // or specify a specific file as the content instead + else + { + // add the content to an anonymous but permanent writer location + // we can then retrieve the URL to the content to to be set on the node during checkin + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); + // also update the mime type in case a different type of file is uploaded + String mimeType = Repository.getMimeTypeForFileName(context, this.fileName); + writer.setMimetype(mimeType); + writer.putContent(this.file); + contentUrl = writer.getContentUrl(); + } + + if (contentUrl == null || contentUrl.length() == 0) + { + throw new IllegalStateException("Content URL is empty for specified working copy content node!"); + } + + // add version history text to props + Map props = new HashMap(1, 1.0f); + props.put(Version.PROP_DESCRIPTION, this.versionNotes); + // set the flag for minor or major change + if (this.minorChange) + { + props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); + } + else + { + props.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + } + + // perform the checkin + this.versionOperationsService.checkin(node.getNodeRef(), + props, contentUrl, this.keepCheckedOut); + + // commit the transaction + tx.commit(); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + + if (this.isWorkflowAction == false) + { + outcome = outcome + AlfrescoNavigationHandler.OUTCOME_SEPARATOR + "browse"; + } + + // clear action context + setDocument(null); + resetState(); + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_CHECKIN) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: checkinFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Action called upon completion of the Update File page + */ + public String updateFileOK() + { + String outcome = null; + + UserTransaction tx = null; + + // NOTE: for update the document node _is_ the working document! + Node node = getDocument(); + if (node != null && this.getFileName() != null) + { + try + { + FacesContext context = FacesContext.getCurrentInstance(); + tx = Repository.getUserTransaction(context); + tx.begin(); + + if (logger.isDebugEnabled()) + logger.debug("Trying to update content node Id: " + node.getId()); + + // get an updating writer that we can use to modify the content on the current node + ContentWriter writer = this.contentService.getWriter(node.getNodeRef(), ContentModel.PROP_CONTENT, true); + + // also update the mime type in case a different type of file is uploaded + String mimeType = Repository.getMimeTypeForFileName(context, this.fileName); + writer.setMimetype(mimeType); + + writer.putContent(this.file); + + // commit the transaction + tx.commit(); + + // clear action context + setDocument(null); + resetState(); + + outcome = AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + catch (Throwable err) + { + // rollback the transaction + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + Utils.addErrorMessage(Application.getMessage( + FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE) + err.getMessage(), err); + } + } + else + { + logger.warn("WARNING: updateFileOK called without a current Document!"); + } + + return outcome; + } + + /** + * Deals with the cancel button being pressed on the check in file page + */ + public String cancel() + { + // reset the state + resetState(); + + return AlfrescoNavigationHandler.CLOSE_DIALOG_OUTCOME; + } + + /** + * Clear form state and upload file bean + */ + private void resetState() + { + // delete the temporary file we uploaded earlier + if (this.file != null) + { + this.file.delete(); + } + + this.file = null; + this.fileName = null; + this.keepCheckedOut = false; + this.minorChange = true; + this.copyLocation = COPYLOCATION_CURRENT; + this.versionNotes = ""; + this.selectedSpaceId = null; + this.isWorkflowAction = false; + this.workflowTaskId = null; + + // remove the file upload bean from the session + FacesContext ctx = FacesContext.getCurrentInstance(); + ctx.getExternalContext().getSessionMap().remove(FileUploadBean.FILE_UPLOAD_BEAN_NAME); + } + + + // ------------------------------------------------------------------------------ + // Private data + + private static Log logger = LogFactory.getLog(CheckinCheckoutBean.class); + + /** I18N messages */ + public static final String MSG_ERROR_CHECKIN = "error_checkin"; + public static final String MSG_ERROR_CANCELCHECKOUT = "error_cancel_checkout"; + public static final String MSG_ERROR_UPDATE = "error_update"; + public static final String MSG_ERROR_CHECKOUT = "error_checkout"; + + /** constants for copy location selection */ + private static final String COPYLOCATION_CURRENT = "current"; + private static final String COPYLOCATION_OTHER = "other"; + + /** The current document */ + private Node document; + + /** The working copy of the document we are checking out */ + private Node workingDocument; + + /** Content of the document used for HTML in-line editing */ + private String documentContent; + + /** Content of the document returned from in-line editing */ + private String editorOutput; + + /** transient form and upload properties */ + private File file; + private String fileName; + private boolean keepCheckedOut = false; + private boolean minorChange = true; + private boolean isWorkflowAction = false; + private String workflowTaskId; + private String copyLocation = COPYLOCATION_CURRENT; + private String versionNotes = ""; + private NodeRef selectedSpaceId = null; + + /** The BrowseBean to be used by the bean */ + protected BrowseBean browseBean; + + /** The NavigationBean bean reference */ + protected NavigationBean navigator; + + /** The NodeService to be used by the bean */ + protected NodeService nodeService; + + /** The VersionOperationsService to be used by the bean */ + protected CheckOutCheckInService versionOperationsService; + + /** The ContentService to be used by the bean */ + protected ContentService contentService; + + /** The WorkflowService to be used by the bean */ + protected WorkflowService workflowService; +} diff --git a/source/java/org/alfresco/web/bean/DocumentDetailsBean.java b/source/java/org/alfresco/web/bean/DocumentDetailsBean.java index 6d8c5eba3a..2659a3009c 100644 --- a/source/java/org/alfresco/web/bean/DocumentDetailsBean.java +++ b/source/java/org/alfresco/web/bean/DocumentDetailsBean.java @@ -29,13 +29,11 @@ import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.transaction.UserTransaction; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.version.Version; @@ -43,9 +41,7 @@ import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.QName; import org.alfresco.web.app.Application; -import org.alfresco.web.app.context.UIContextService; import org.alfresco.web.app.servlet.DownloadContentServlet; -import org.alfresco.web.bean.actions.handlers.SimpleWorkflowHandler; import org.alfresco.web.bean.repository.MapNode; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; @@ -68,17 +64,12 @@ public class DocumentDetailsBean extends BaseDetailsBean private static final String MSG_ERROR_ASPECT_INLINEEDITABLE = "error_aspect_inlineeditable"; private static final String MSG_ERROR_ASPECT_VERSIONING = "error_aspect_versioning"; private static final String MSG_ERROR_ASPECT_CLASSIFY = "error_aspect_classify"; - private static final String MSG_ERROR_WORKFLOW_REJECT = "error_workflow_reject"; - private static final String MSG_ERROR_WORKFLOW_APPROVE = "error_workflow_approve"; - private static final String MSG_ERROR_UPDATE_SIMPLEWORKFLOW = "error_update_simpleworkflow"; private static final String MSG_ERROR_UPDATE_CATEGORY = "error_update_category"; protected LockService lockService; - protected CopyService copyService; protected VersionService versionService; protected CheckOutCheckInService cociService; - private Map workflowProperties; private NodeRef addedCategory; private List categories; @@ -91,9 +82,9 @@ public class DocumentDetailsBean extends BaseDetailsBean */ public DocumentDetailsBean() { + super(); + // initial state of some panels that don't use the default - panels.put("workflow-panel", false); - panels.put("category-panel", false); panels.put("version-history-panel", false); } @@ -369,391 +360,6 @@ public class DocumentDetailsBean extends BaseDetailsBean return outcome; } - /** - * Returns an overview summary of the current state of the attached - * workflow (if any) - * - * @return Summary HTML - */ - public String getWorkflowOverviewHTML() - { - String html = null; - - if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) - { - // get the simple workflow aspect properties - Map props = getDocument().getProperties(); - - String approveStepName = (String)props.get( - ContentModel.PROP_APPROVE_STEP.toString()); - String rejectStepName = (String)props.get( - ContentModel.PROP_REJECT_STEP.toString()); - - Boolean approveMove = (Boolean)props.get( - ContentModel.PROP_APPROVE_MOVE.toString()); - Boolean rejectMove = (Boolean)props.get( - ContentModel.PROP_REJECT_MOVE.toString()); - - NodeRef approveFolder = (NodeRef)props.get( - ContentModel.PROP_APPROVE_FOLDER.toString()); - NodeRef rejectFolder = (NodeRef)props.get( - ContentModel.PROP_REJECT_FOLDER.toString()); - - String approveFolderName = null; - String rejectFolderName = null; - - // get the approve folder name - if (approveFolder != null) - { - Node node = new Node(approveFolder); - approveFolderName = node.getName(); - } - - // get the reject folder name - if (rejectFolder != null) - { - Node node = new Node(rejectFolder); - rejectFolderName = node.getName(); - } - - StringBuilder builder = new StringBuilder(); - - // calculate the approve action string - String action = null; - if (approveMove.booleanValue()) - { - action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); - } - else - { - action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); - } - - String docActionPattern = Application.getMessage(FacesContext.getCurrentInstance(), "document_action"); - Object[] params = new Object[] {action, approveFolderName, approveStepName}; - builder.append(MessageFormat.format(docActionPattern, params)); - - // add details of the reject step if there is one - if (rejectStepName != null && rejectMove != null && rejectFolderName != null) - { - if (rejectMove.booleanValue()) - { - action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); - } - else - { - action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); - } - - builder.append("

"); - params = new Object[] {action, rejectFolderName, rejectStepName}; - builder.append(MessageFormat.format(docActionPattern, params)); - builder.append("

"); - } - - html = builder.toString(); - } - - return html; - } - - /** - * Returns the properties for the attached workflow as a map - * - * @return Properties of the attached workflow, null if there is no workflow - */ - public Map getWorkflowProperties() - { - if (this.workflowProperties == null && - getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) - { - // get the exisiting properties for the document - Map props = getDocument().getProperties(); - - String approveStepName = (String)props.get( - ContentModel.PROP_APPROVE_STEP.toString()); - String rejectStepName = (String)props.get( - ContentModel.PROP_REJECT_STEP.toString()); - - Boolean approveMove = (Boolean)props.get( - ContentModel.PROP_APPROVE_MOVE.toString()); - Boolean rejectMove = (Boolean)props.get( - ContentModel.PROP_REJECT_MOVE.toString()); - - NodeRef approveFolder = (NodeRef)props.get( - ContentModel.PROP_APPROVE_FOLDER.toString()); - NodeRef rejectFolder = (NodeRef)props.get( - ContentModel.PROP_REJECT_FOLDER.toString()); - - // put the workflow properties in a separate map for use by the JSP - this.workflowProperties = new HashMap(7); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_STEP_NAME, - approveStepName); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_ACTION, - approveMove ? "move" : "copy"); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_APPROVE_FOLDER, approveFolder); - - if (rejectStepName == null || rejectMove == null || rejectFolder == null) - { - this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT, "no"); - } - else - { - this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT, - "yes"); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_STEP_NAME, - rejectStepName); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_ACTION, - rejectMove ? "move" : "copy"); - this.workflowProperties.put(SimpleWorkflowHandler.PROP_REJECT_FOLDER, - rejectFolder); - } - } - - return this.workflowProperties; - } - - /** - * Cancel Workflow Edit dialog - */ - public String cancelWorkflowEdit() - { - // resets the workflow properties map so any changes made - // don't appear to be persisted - this.workflowProperties.clear(); - this.workflowProperties = null; - return "cancel"; - } - - /** - * Saves the details of the workflow stored in workflowProperties - * to the current document - * - * @return The outcome string - */ - public String saveWorkflow() - { - String outcome = "cancel"; - - UserTransaction tx = null; - - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); - tx.begin(); - - // firstly retrieve all the properties for the current node - Map updateProps = this.nodeService.getProperties( - getDocument().getNodeRef()); - - // update the simple workflow properties - - // set the approve step name - updateProps.put(ContentModel.PROP_APPROVE_STEP, - this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_STEP_NAME)); - - // specify whether the approve step will copy or move the content - boolean approveMove = true; - String approveAction = (String)this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_ACTION); - if (approveAction != null && approveAction.equals("copy")) - { - approveMove = false; - } - updateProps.put(ContentModel.PROP_APPROVE_MOVE, Boolean.valueOf(approveMove)); - - // create node ref representation of the destination folder - updateProps.put(ContentModel.PROP_APPROVE_FOLDER, - this.workflowProperties.get(SimpleWorkflowHandler.PROP_APPROVE_FOLDER)); - - // determine whether there should be a reject step - boolean requireReject = true; - String rejectStepPresent = (String)this.workflowProperties.get( - SimpleWorkflowHandler.PROP_REJECT_STEP_PRESENT); - if (rejectStepPresent != null && rejectStepPresent.equals("no")) - { - requireReject = false; - } - - if (requireReject) - { - // set the reject step name - updateProps.put(ContentModel.PROP_REJECT_STEP, - this.workflowProperties.get(SimpleWorkflowHandler.PROP_REJECT_STEP_NAME)); - - // specify whether the reject step will copy or move the content - boolean rejectMove = true; - String rejectAction = (String)this.workflowProperties.get( - SimpleWorkflowHandler.PROP_REJECT_ACTION); - if (rejectAction != null && rejectAction.equals("copy")) - { - rejectMove = false; - } - updateProps.put(ContentModel.PROP_REJECT_MOVE, Boolean.valueOf(rejectMove)); - - // create node ref representation of the destination folder - updateProps.put(ContentModel.PROP_REJECT_FOLDER, - this.workflowProperties.get(SimpleWorkflowHandler.PROP_REJECT_FOLDER)); - } - else - { - // set all the reject properties to null to signify there should - // be no reject step - updateProps.put(ContentModel.PROP_REJECT_STEP, null); - updateProps.put(ContentModel.PROP_REJECT_MOVE, null); - updateProps.put(ContentModel.PROP_REJECT_FOLDER, null); - } - - // set the properties on the node - this.nodeService.setProperties(getDocument().getNodeRef(), updateProps); - - // commit the transaction - tx.commit(); - - // reset the state of the current document so it reflects the changes just made - getDocument().reset(); - - outcome = "finish"; - } - catch (Throwable e) - { - try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_UPDATE_SIMPLEWORKFLOW), e.getMessage()), e); - } - - return outcome; - } - - /** - * Returns the name of the approve step of the attached workflow - * - * @return The name of the approve step or null if there is no workflow - */ - public String getApproveStepName() - { - String approveStepName = null; - - if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) - { - approveStepName = (String)getDocument().getProperties().get( - ContentModel.PROP_APPROVE_STEP.toString()); - } - - return approveStepName; - } - - /** - * Event handler called to handle the approve step of the simple workflow - * - * @param event The event that was triggered - */ - public void approve(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - if (id == null || id.length() == 0) - { - throw new AlfrescoRuntimeException("approve called without an id"); - } - - NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); - - UserTransaction tx = null; - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); - tx.begin(); - - // call the service to perform the approve - WorkflowUtil.approve(docNodeRef, this.nodeService, this.copyService); - - // commit the transaction - tx.commit(); - - // if this was called via the document details dialog we need to reset the document node - if (getDocument() != null) - { - getDocument().reset(); - } - - // also make sure the UI will get refreshed - UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); - } - catch (Throwable e) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_APPROVE), e.getMessage()), e); - } - } - - /** - * Returns the name of the reject step of the attached workflow - * - * @return The name of the reject step or null if there is no workflow - */ - public String getRejectStepName() - { - String approveStepName = null; - - if (getDocument().hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) - { - approveStepName = (String)getDocument().getProperties().get( - ContentModel.PROP_REJECT_STEP.toString()); - } - - return approveStepName; - } - - /** - * Event handler called to handle the approve step of the simple workflow - * - * @param event The event that was triggered - */ - public void reject(ActionEvent event) - { - UIActionLink link = (UIActionLink)event.getComponent(); - Map params = link.getParameterMap(); - String id = params.get("id"); - if (id == null || id.length() == 0) - { - throw new AlfrescoRuntimeException("reject called without an id"); - } - - NodeRef docNodeRef = new NodeRef(Repository.getStoreRef(), id); - - UserTransaction tx = null; - try - { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance()); - tx.begin(); - - // call the service to perform the reject - WorkflowUtil.reject(docNodeRef, this.nodeService, this.copyService); - - // commit the transaction - tx.commit(); - - // if this was called via the document details dialog we need to reset the document node - if (getDocument() != null) - { - getDocument().reset(); - } - - // also make sure the UI will get refreshed - UIContextService.getInstance(FacesContext.getCurrentInstance()).notifyBeans(); - } - catch (Throwable e) - { - // rollback the transaction - try { if (tx != null) {tx.rollback();} } catch (Exception ex) {} - Utils.addErrorMessage(MessageFormat.format(Application.getMessage( - FacesContext.getCurrentInstance(), MSG_ERROR_WORKFLOW_REJECT), e.getMessage()), e); - } - } - /** * Applies the classifiable aspect to the current document */ @@ -1099,16 +705,6 @@ public class DocumentDetailsBean extends BaseDetailsBean this.versionService = versionService; } - /** - * Sets the copy service instance the bean should use - * - * @param copyService The CopyService - */ - public void setCopyService(CopyService copyService) - { - this.copyService = copyService; - } - /** * Sets the checkincheckout service instance the bean should use * diff --git a/source/java/org/alfresco/web/bean/ErrorBean.java b/source/java/org/alfresco/web/bean/ErrorBean.java index a7429d264e..0ddeeee0cc 100644 --- a/source/java/org/alfresco/web/bean/ErrorBean.java +++ b/source/java/org/alfresco/web/bean/ErrorBean.java @@ -121,16 +121,20 @@ public class ErrorBean */ public String getStackTrace() { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - if (this.lastError != null) - this.lastError.printStackTrace(writer); + String trace = "No stack trace available"; - // format the message for HTML display - String trace = stringWriter.toString(); - trace = trace.replaceAll("<", "<"); - trace = trace.replaceAll(">", ">"); - trace = trace.replaceAll("\n", "
"); + if (this.lastError != null) + { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + this.lastError.printStackTrace(writer); + + // format the message for HTML display + trace = stringWriter.toString(); + trace = trace.replaceAll("<", "<"); + trace = trace.replaceAll(">", ">"); + trace = trace.replaceAll("\n", "
"); + } return trace; } diff --git a/source/java/org/alfresco/web/bean/SpaceDetailsBean.java b/source/java/org/alfresco/web/bean/SpaceDetailsBean.java index 234a37ecdb..39b0793ad3 100644 --- a/source/java/org/alfresco/web/bean/SpaceDetailsBean.java +++ b/source/java/org/alfresco/web/bean/SpaceDetailsBean.java @@ -71,8 +71,11 @@ public class SpaceDetailsBean extends BaseDetailsBean */ public SpaceDetailsBean() { + super(); + // initial state of some panels that don't use the default panels.put("rules-panel", false); + panels.put("dashboard-panel", false); } diff --git a/source/java/org/alfresco/web/bean/TrashcanBean.java b/source/java/org/alfresco/web/bean/TrashcanBean.java index aa1d88f5a1..eba6e8ee2f 100644 --- a/source/java/org/alfresco/web/bean/TrashcanBean.java +++ b/source/java/org/alfresco/web/bean/TrashcanBean.java @@ -157,6 +157,8 @@ public class TrashcanBean implements IContextListener /** User filter search box text */ private String userSearchText = null; + private boolean inProgress = false; + // ------------------------------------------------------------------------------ // Bean property getters and setters @@ -784,43 +786,54 @@ public class TrashcanBean implements IContextListener */ public String recoverListedItemsOK() { - FacesContext fc = FacesContext.getCurrentInstance(); + if (inProgress == true) return null; - // restore the nodes - the user may have requested a restore to a different parent - List nodeRefs = new ArrayList(this.listedItems.size()); - for (Node node : this.listedItems) - { - nodeRefs.add(node.getNodeRef()); - } - List reports; - if (this.destination == null) - { - reports = this.nodeArchiveService.restoreArchivedNodes(nodeRefs); - } - else - { - reports = this.nodeArchiveService.restoreArchivedNodes(nodeRefs, this.destination, null, null); - } + inProgress = true; - UserTransaction tx = null; try { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); - tx.begin(); + FacesContext fc = FacesContext.getCurrentInstance(); - saveReportDetail(reports); + // restore the nodes - the user may have requested a restore to a different parent + List nodeRefs = new ArrayList(this.listedItems.size()); + for (Node node : this.listedItems) + { + nodeRefs.add(node.getNodeRef()); + } + List reports; + if (this.destination == null) + { + reports = this.nodeArchiveService.restoreArchivedNodes(nodeRefs); + } + else + { + reports = this.nodeArchiveService.restoreArchivedNodes(nodeRefs, this.destination, null, null); + } - tx.commit(); + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + saveReportDetail(reports); + + tx.commit(); + } + catch (Throwable err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + // most exceptions will be caught and returned as RestoreNodeReport objects by the service + String reason = err.getMessage(); + String msg = MessageFormat.format( + Application.getMessage(fc, Repository.ERROR_GENERIC), reason); + FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); + fc.addMessage(null, facesMsg); + } } - catch (Throwable err) + finally { - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - // most exceptions will be caught and returned as RestoreNodeReport objects by the service - String reason = err.getMessage(); - String msg = MessageFormat.format( - Application.getMessage(fc, Repository.ERROR_GENERIC), reason); - FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); - fc.addMessage(null, facesMsg); + inProgress = false; } return OUTCOME_RECOVERY_REPORT; @@ -831,38 +844,49 @@ public class TrashcanBean implements IContextListener */ public String recoverAllItemsOK() { - FacesContext fc = FacesContext.getCurrentInstance(); + if (inProgress == true) return null; - // restore all nodes - the user may have requested a restore to a different parent - List reports; - if (this.destination == null) - { - reports = this.nodeArchiveService.restoreAllArchivedNodes(Repository.getStoreRef()); - } - else - { - reports = this.nodeArchiveService.restoreAllArchivedNodes(Repository.getStoreRef(), this.destination, null, null); - } + inProgress = true; - UserTransaction tx = null; try { - tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); - tx.begin(); + FacesContext fc = FacesContext.getCurrentInstance(); - saveReportDetail(reports); + // restore all nodes - the user may have requested a restore to a different parent + List reports; + if (this.destination == null) + { + reports = this.nodeArchiveService.restoreAllArchivedNodes(Repository.getStoreRef()); + } + else + { + reports = this.nodeArchiveService.restoreAllArchivedNodes(Repository.getStoreRef(), this.destination, null, null); + } - tx.commit(); + UserTransaction tx = null; + try + { + tx = Repository.getUserTransaction(FacesContext.getCurrentInstance(), true); + tx.begin(); + + saveReportDetail(reports); + + tx.commit(); + } + catch (Throwable err) + { + try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} + // most exceptions will be caught and returned as RestoreNodeReport objects by the service + String reason = err.getMessage(); + String msg = MessageFormat.format( + Application.getMessage(fc, Repository.ERROR_GENERIC), reason); + FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); + fc.addMessage(null, facesMsg); + } } - catch (Throwable err) + finally { - try { if (tx != null) {tx.rollback();} } catch (Exception tex) {} - // most exceptions will be caught and returned as RestoreNodeReport objects by the service - String reason = err.getMessage(); - String msg = MessageFormat.format( - Application.getMessage(fc, Repository.ERROR_GENERIC), reason); - FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); - fc.addMessage(null, facesMsg); + inProgress = false; } return OUTCOME_RECOVERY_REPORT; @@ -883,6 +907,10 @@ public class TrashcanBean implements IContextListener */ public String deleteListedItemsOK() { + if (inProgress == true) return null; + + inProgress = true; + try { List nodeRefs = new ArrayList(this.listedItems.size()); @@ -897,6 +925,10 @@ public class TrashcanBean implements IContextListener Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); } + finally + { + inProgress = false; + } return OUTCOME_DIALOGCLOSE; } @@ -906,6 +938,10 @@ public class TrashcanBean implements IContextListener */ public String deleteAllItemsOK() { + if (inProgress == true) return null; + + inProgress = true; + try { this.nodeArchiveService.purgeAllArchivedNodes(Repository.getStoreRef()); @@ -915,6 +951,10 @@ public class TrashcanBean implements IContextListener Utils.addErrorMessage(MessageFormat.format(Application.getMessage( FacesContext.getCurrentInstance(), Repository.ERROR_GENERIC), err.getMessage()), err); } + finally + { + inProgress = false; + } return OUTCOME_DIALOGCLOSE; } diff --git a/source/java/org/alfresco/web/bean/WorkflowUtil.java b/source/java/org/alfresco/web/bean/WorkflowUtil.java index 8f8ed9077d..fa57b332d7 100644 --- a/source/java/org/alfresco/web/bean/WorkflowUtil.java +++ b/source/java/org/alfresco/web/bean/WorkflowUtil.java @@ -55,7 +55,7 @@ public class WorkflowUtil if (docNode.hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW) == false) { - throw new AlfrescoRuntimeException("Cannot approve a document that is not part of a workflow."); + throw new AlfrescoRuntimeException("Cannot approve a node that is not part of a workflow."); } // get the simple workflow aspect properties @@ -69,23 +69,28 @@ public class WorkflowUtil if (approveMove.booleanValue()) { - // move the document to the specified folder + // move the node to the specified folder String qname = QName.createValidLocalName(docNode.getName()); nodeService.moveNode(ref, approveFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); } else { - // copy the document to the specified folder - String qname = QName.createValidLocalName(docNode.getName()); - copyService.copy(ref, approveFolder, ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname)); + // copy the node to the specified folder + String name = docNode.getName(); + String qname = QName.createValidLocalName(name); + NodeRef newNode = copyService.copy(ref, approveFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, qname), true); + + // the copy service does not copy the name of the node so we + // need to update the property on the copied item + nodeService.setProperty(newNode, ContentModel.PROP_NAME, name); } if (logger.isDebugEnabled()) { String movedCopied = approveMove ? "moved" : "copied"; - logger.debug("Document has been approved and " + movedCopied + " to folder with id of " + + logger.debug("Node has been approved and " + movedCopied + " to folder with id of " + approveFolder.getId()); } } @@ -106,7 +111,7 @@ public class WorkflowUtil if (docNode.hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW) == false) { - throw new AlfrescoRuntimeException("Cannot reject a document that is not part of a workflow."); + throw new AlfrescoRuntimeException("Cannot reject a node that is not part of a workflow."); } // get the simple workflow aspect properties @@ -118,7 +123,7 @@ public class WorkflowUtil if (rejectStep == null && rejectMove == null && rejectFolder == null) { - throw new AlfrescoRuntimeException("The workflow does not have a reject step defined,"); + throw new AlfrescoRuntimeException("The workflow does not have a reject step defined."); } // first we need to take off the simpleworkflow aspect @@ -142,7 +147,7 @@ public class WorkflowUtil if (logger.isDebugEnabled()) { String movedCopied = rejectMove ? "moved" : "copied"; - logger.debug("Document has been rejected and " + movedCopied + " to folder with id of " + + logger.debug("Node has been rejected and " + movedCopied + " to folder with id of " + rejectFolder.getId()); } } diff --git a/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java b/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java index af6e8ae556..edaa8f7dea 100644 --- a/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java +++ b/source/java/org/alfresco/web/bean/content/EditContentPropertiesDialog.java @@ -42,7 +42,7 @@ public class EditContentPropertiesDialog extends BaseDialogBean super.init(parameters); // setup the editable node - this.editableNode = initEditableNode(); + this.editableNode = new Node(this.browseBean.getDocument().getNodeRef()); // special case for Mimetype - since this is a sub-property of the ContentData object // we must extract it so it can be edited in the client, then we check for it later @@ -54,19 +54,11 @@ public class EditContentPropertiesDialog extends BaseDialogBean } } - /** - * Init the editable Node - */ - protected Node initEditableNode() - { - return new Node(this.browseBean.getDocument().getNodeRef()); - } - @Override protected String finishImpl(FacesContext context, String outcome) throws Exception { - NodeRef nodeRef = this.editableNode.getNodeRef(); + NodeRef nodeRef = this.browseBean.getDocument().getNodeRef(); Map editedProps = this.editableNode.getProperties(); // get the name and move the node as necessary @@ -76,9 +68,9 @@ public class EditContentPropertiesDialog extends BaseDialogBean fileFolderService.rename(nodeRef, name); } + Map repoProps = this.nodeService.getProperties(nodeRef); // we need to put all the properties from the editable bag back into // the format expected by the repository - Map repoProps = this.nodeService.getProperties(nodeRef); // but first extract and deal with the special mimetype property for ContentData String mimetype = (String)editedProps.get(TEMP_PROP_MIMETYPE); @@ -180,7 +172,7 @@ public class EditContentPropertiesDialog extends BaseDialogBean this.nodeService.removeChild(assoc.getParentRef(), assoc.getChildRef()); } } - + return outcome; } @@ -241,7 +233,6 @@ public class EditContentPropertiesDialog extends BaseDialogBean return false; } - // ------------------------------------------------------------------------------ // Bean getters and setters diff --git a/source/java/org/alfresco/web/bean/dialog/DialogManager.java b/source/java/org/alfresco/web/bean/dialog/DialogManager.java index abeff99b4d..f4238e9131 100644 --- a/source/java/org/alfresco/web/bean/dialog/DialogManager.java +++ b/source/java/org/alfresco/web/bean/dialog/DialogManager.java @@ -48,13 +48,27 @@ public final class DialogManager */ public void setCurrentDialog(DialogConfig config) { + // make sure the managed bean is present String beanName = config.getManagedBean(); - IDialogBean dialog = (IDialogBean)FacesHelper.getManagedBean( - FacesContext.getCurrentInstance(), beanName); - if (dialog == null) + Object bean = FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), beanName); + + if (bean == null) { - throw new AlfrescoRuntimeException("Failed to find managed bean '" + beanName + "'"); + throw new AlfrescoRuntimeException("Failed to start dialog as managed bean '" + beanName + + "' has not been defined"); + } + + // make sure the bean implements the IDialogBean interface + IDialogBean dialog = null; + if (bean instanceof IDialogBean) + { + dialog = (IDialogBean)bean; + } + else + { + throw new AlfrescoRuntimeException("Failed to start dialog as managed bean '" + beanName + + "' does not implement the required IDialogBean interface"); } // initialise the managed bean diff --git a/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java b/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java index 0e2d9e928d..00a8f9fb4f 100644 --- a/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/CategorySelectorGenerator.java @@ -5,6 +5,7 @@ import javax.faces.context.FacesContext; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.repo.RepoConstants; +import org.alfresco.web.ui.repo.component.UIMultiValueEditor; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; @@ -37,14 +38,29 @@ public class CategorySelectorGenerator extends BaseComponentGenerator protected void setupMandatoryValidation(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item, UIComponent component, boolean realTimeChecking, String idSuffix) { - // Override the setup of the mandatory validation - // so we can send the _selected id suffix. - // We also enable real time so the page load check disabled - // the ok button if necessary, as the control is used the - // page will be refreshed and therefore re-check the status. - - super.setupMandatoryValidation(context, propertySheet, item, - component, true, "_selected"); + if (component instanceof UIMultiValueEditor) + { + // Override the setup of the mandatory validation + // so we can send the _current_value id suffix. + // We also enable real time so the page load + // check disables the ok button if necessary, as the user + // adds or removes items from the multi value list the + // page will be refreshed and therefore re-check the status. + + super.setupMandatoryValidation(context, propertySheet, item, + component, true, "_current_value"); + } + else + { + // Override the setup of the mandatory validation + // so we can send the _selected id suffix. + // We also enable real time so the page load check disabled + // the ok button if necessary, as the control is used the + // page will be refreshed and therefore re-check the status. + + super.setupMandatoryValidation(context, propertySheet, item, + component, true, "_selected"); + } } @Override diff --git a/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java b/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java index a7f0463ddb..7138673285 100644 --- a/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/DatePickerGenerator.java @@ -4,7 +4,6 @@ import java.util.Date; import javax.faces.component.UIComponent; import javax.faces.component.UIOutput; -import javax.faces.component.UISelectOne; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; @@ -25,6 +24,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet; */ public class DatePickerGenerator extends BaseComponentGenerator { + private boolean initialiseIfNull = false; private int yearCount = 30; private int startYear = new Date().getYear() + 1900 + 2; @@ -61,6 +61,26 @@ public class DatePickerGenerator extends BaseComponentGenerator { this.yearCount = yearCount; } + + /** + * @return Determines whether the control should initially show + * today's date if the model value is null + */ + public boolean isInitialiseIfNull() + { + return initialiseIfNull; + } + + /** + * @param initialiseIfNull Determines whether the control should + * initially show today's date if the model value is null. + * This will also hide the None button thus disallowing + * the user to set the date back to null. + */ + public void setInitialiseIfNull(boolean initialiseIfNull) + { + this.initialiseIfNull = initialiseIfNull; + } @SuppressWarnings("unchecked") public UIComponent generate(FacesContext context, String id) @@ -71,6 +91,7 @@ public class DatePickerGenerator extends BaseComponentGenerator FacesHelper.setupComponentId(context, component, id); component.getAttributes().put("startYear", this.startYear); component.getAttributes().put("yearCount", this.yearCount); + component.getAttributes().put("initialiseIfNull", new Boolean(this.initialiseIfNull)); component.getAttributes().put("style", "margin-right: 7px;"); return component; diff --git a/source/java/org/alfresco/web/bean/generator/HtmlSeparatorGenerator.java b/source/java/org/alfresco/web/bean/generator/HtmlSeparatorGenerator.java index 3e8153750b..8fe3079b3d 100644 --- a/source/java/org/alfresco/web/bean/generator/HtmlSeparatorGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/HtmlSeparatorGenerator.java @@ -15,7 +15,7 @@ import org.alfresco.web.ui.repo.component.property.UIPropertySheet; */ public class HtmlSeparatorGenerator extends BaseComponentGenerator { - protected String html = "default"; + protected String html = ""; /** * Returns the HTML configured to be used for this separator diff --git a/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java b/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java index cdbe96d52e..053438fbd8 100644 --- a/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java +++ b/source/java/org/alfresco/web/bean/generator/TextFieldGenerator.java @@ -1,5 +1,6 @@ package org.alfresco.web.bean.generator; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -12,12 +13,15 @@ import javax.faces.model.SelectItem; import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.web.app.Application; import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.ui.common.ComponentConstants; import org.alfresco.web.ui.repo.component.UIMultiValueEditor; import org.alfresco.web.ui.repo.component.property.PropertySheetItem; import org.alfresco.web.ui.repo.component.property.UIPropertySheet; +import org.alfresco.web.ui.repo.component.property.UIPropertySheet.ClientValidation; /** * Generates a text field component. @@ -166,6 +170,47 @@ public class TextFieldGenerator extends BaseComponentGenerator } } + @Override + @SuppressWarnings("unchecked") + protected void setupConstraints(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem property, + PropertyDefinition propertyDef, UIComponent component) + { + // do the default processing first + super.setupConstraints(context, propertySheet, property, + propertyDef, component); + + // if the property type is a number based type and the property + // sheet is in edit mode and validation is turned, on add the + // validateIsNumber validation function + if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && + propertyDef != null) + { + // check the type of the property is a number + if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DOUBLE) || + propertyDef.getDataType().getName().equals(DataTypeDefinition.FLOAT) || + propertyDef.getDataType().getName().equals(DataTypeDefinition.INT) || + propertyDef.getDataType().getName().equals(DataTypeDefinition.LONG)) + { + List params = new ArrayList(3); + + // add the value parameter + String value = "document.getElementById('" + + component.getClientId(context) + "')"; + params.add(value); + + // add the validation failed message to show + String msg = Application.getMessage(context, "validation_is_number"); + addStringConstraintParam(params, + MessageFormat.format(msg, new Object[] {property.getResolvedDisplayLabel()})); + + // add the validation case to the property sheet + propertySheet.addClientValidation(new ClientValidation("validateIsNumber", + params, false)); + } + } + } + /** * Retrieves the list of values constraint for the item, if it has one * @@ -174,8 +219,8 @@ public class TextFieldGenerator extends BaseComponentGenerator * @param item The item being generated * @return The constraint if the item has one, null otherwise */ - protected ListOfValuesConstraint getListOfValuesConstraint(FacesContext context, UIPropertySheet propertySheet, - PropertySheetItem item) + protected ListOfValuesConstraint getListOfValuesConstraint(FacesContext context, + UIPropertySheet propertySheet, PropertySheetItem item) { ListOfValuesConstraint lovConstraint = null; diff --git a/source/java/org/alfresco/web/bean/repository/User.java b/source/java/org/alfresco/web/bean/repository/User.java index 99bbd61bc3..027ded644d 100644 --- a/source/java/org/alfresco/web/bean/repository/User.java +++ b/source/java/org/alfresco/web/bean/repository/User.java @@ -16,7 +16,9 @@ */ package org.alfresco.web.bean.repository; +import java.io.Serializable; import java.util.List; +import java.util.Map; import javax.faces.context.FacesContext; @@ -213,4 +215,21 @@ public final class User return prefRef; } + + /** + * Returns the full name of the user represented by the given NodeRef + * + * @param nodeService The node service instance + * @param user The user to get the full name for + * @return The full name + */ + public static String getFullName(NodeService nodeService, NodeRef user) + { + Map props = nodeService.getProperties(user); + String firstName = (String)props.get(ContentModel.PROP_FIRSTNAME); + String lastName = (String)props.get(ContentModel.PROP_LASTNAME); + String fullName = firstName + " " + (lastName != null ? lastName : ""); + + return fullName; + } } diff --git a/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java b/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java index e4bfa8a3b3..99ef877d3f 100644 --- a/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java +++ b/source/java/org/alfresco/web/bean/spaces/EditSpaceDialog.java @@ -31,9 +31,15 @@ public class EditSpaceDialog extends CreateSpaceDialog super.init(parameters); // setup the space being edited - this.editableNode = initEditableNode(); + this.editableNode = new Node(this.browseBean.getActionSpace().getNodeRef()); this.spaceType = this.editableNode.getType().toString(); } + + @Override + public boolean getFinishButtonDisabled() + { + return false; + } /** * Init the editable Node @@ -49,27 +55,11 @@ public class EditSpaceDialog extends CreateSpaceDialog return Application.getMessage(FacesContext.getCurrentInstance(), "ok"); } - @Override - public boolean getFinishButtonDisabled() - { - return false; - } - - /** - * Returns the editable node - * - * @return The editable node - */ - public Node getEditableNode() - { - return this.editableNode; - } - @Override protected String finishImpl(FacesContext context, String outcome) throws Exception { // update the existing node in the repository - NodeRef nodeRef = this.editableNode.getNodeRef(); + NodeRef nodeRef = this.browseBean.getActionSpace().getNodeRef(); Map editedProps = this.editableNode.getProperties(); // handle the name property separately, perform a rename in case it changed @@ -162,7 +152,7 @@ public class EditSpaceDialog extends CreateSpaceDialog this.nodeService.removeChild(assoc.getParentRef(), assoc.getChildRef()); } } - + return outcome; } diff --git a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java index 54c2629f5a..45698afaf3 100644 --- a/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java +++ b/source/java/org/alfresco/web/bean/wcm/AVMBrowseBean.java @@ -732,25 +732,25 @@ public class AVMBrowseBean implements IContextListener tx = Repository.getUserTransaction(context, false); tx.begin(); - NodeRef nodeRef = getAvmActionNode().getNodeRef(); - String name = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - NodeRef workflowPackage = this.workflowService.createPackage(null); - ChildAssociationRef childRef = - this.nodeService.createNode(workflowPackage, ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, - name), ContentModel.TYPE_CMOBJECT); - Map aspectProperties = new HashMap(1); - aspectProperties.put(ContentModel.PROP_NODE_REF, nodeRef); - NodeRef childNodeRef = childRef.getChildRef(); - this.nodeService.addAspect(childNodeRef, ContentModel.ASPECT_REFERENCES_NODE, aspectProperties); - - Map actionParams = new HashMap(); - actionParams.put(StartAVMWorkflowAction.PARAM_WORKFLOW_NAME, "jbpm$wcmwf:submit"); - Action action = this.actionService.createAction(ACTION_AVM_WORKFLOW, actionParams); - this.actionService.executeAction(action, workflowPackage); +// NodeRef nodeRef = getAvmActionNode().getNodeRef(); +// String name = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); +// NodeRef workflowPackage = this.workflowService.createPackage(null); +// ChildAssociationRef childRef = +// this.nodeService.createNode(workflowPackage, ContentModel.ASSOC_CONTAINS, +// QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, +// name), ContentModel.TYPE_CMOBJECT); +// Map aspectProperties = new HashMap(1); +// aspectProperties.put(ContentModel.PROP_NODE_REF, nodeRef); +// NodeRef childNodeRef = childRef.getChildRef(); +// this.nodeService.addAspect(childNodeRef, ContentModel.ASPECT_REFERENCES_NODE, aspectProperties); +// +// Map actionParams = new HashMap(); +// actionParams.put(StartAVMWorkflowAction.PARAM_WORKFLOW_NAME, "jbpm$wcmwf:submit"); +// Action action = this.actionService.createAction(ACTION_AVM_WORKFLOW, actionParams); +// this.actionService.executeAction(action, workflowPackage); - // Action action = this.actionService.createAction(ACTION_AVM_SUBMIT); - // this.actionService.executeAction(action, getAvmActionNode().getNodeRef()); + Action action = this.actionService.createAction(ACTION_AVM_SUBMIT); + this.actionService.executeAction(action, getAvmActionNode().getNodeRef()); // commit the transaction tx.commit(); diff --git a/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java b/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java index e349888986..e0685838d9 100644 --- a/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java +++ b/source/java/org/alfresco/web/bean/wcm/EditFilePropertiesDialog.java @@ -75,7 +75,6 @@ public class EditFilePropertiesDialog extends EditContentPropertiesDialog /** * @see org.alfresco.web.bean.content.EditContentPropertiesDialog#initEditableNode() */ - @Override protected Node initEditableNode() { return new Node(this.avmBrowseBean.getAvmActionNode().getNodeRef()); diff --git a/source/java/org/alfresco/web/bean/wizard/WizardManager.java b/source/java/org/alfresco/web/bean/wizard/WizardManager.java index 333f236118..6d705ab83f 100644 --- a/source/java/org/alfresco/web/bean/wizard/WizardManager.java +++ b/source/java/org/alfresco/web/bean/wizard/WizardManager.java @@ -56,13 +56,27 @@ public final class WizardManager */ public void setCurrentWizard(WizardConfig config) { + // make sure the managed bean is present String beanName = config.getManagedBean(); - IWizardBean wizard = (IWizardBean)FacesHelper.getManagedBean( - FacesContext.getCurrentInstance(), beanName); - if (wizard == null) + Object bean = FacesHelper.getManagedBean(FacesContext.getCurrentInstance(), beanName); + + if (bean == null) { - throw new AlfrescoRuntimeException("Failed to find managed bean '" + beanName + "'"); + throw new AlfrescoRuntimeException("Failed to start wizard as managed bean '" + beanName + + "' has not been defined"); + } + + // make sure the bean implements the IWizardBean interface + IWizardBean wizard = null; + if (bean instanceof IWizardBean) + { + wizard = (IWizardBean)bean; + } + else + { + throw new AlfrescoRuntimeException("Failed to start wizard as managed bean '" + beanName + + "' does not implement the required IWizardBean interface"); } // initialise the managed bean diff --git a/source/java/org/alfresco/web/bean/workflow/CancelWorkflowDialog.java b/source/java/org/alfresco/web/bean/workflow/CancelWorkflowDialog.java index c9c0b78964..f9176eb878 100644 --- a/source/java/org/alfresco/web/bean/workflow/CancelWorkflowDialog.java +++ b/source/java/org/alfresco/web/bean/workflow/CancelWorkflowDialog.java @@ -5,6 +5,7 @@ import java.util.Map; import javax.faces.context.FacesContext; +import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.web.app.Application; import org.alfresco.web.bean.dialog.BaseDialogBean; @@ -18,7 +19,7 @@ import org.apache.commons.logging.LogFactory; */ public class CancelWorkflowDialog extends BaseDialogBean { - protected String workflowInstanceId; + protected WorkflowInstance workflowInstance; protected WorkflowService workflowService; private static final Log logger = LogFactory.getLog(CancelWorkflowDialog.class); @@ -32,11 +33,17 @@ public class CancelWorkflowDialog extends BaseDialogBean super.init(parameters); // make sure the workflow instance id has been passed - this.workflowInstanceId = this.parameters.get("workflow-instance-id"); - if (this.workflowInstanceId == null || this.workflowInstanceId.length() == 0) + String workflowInstanceId = this.parameters.get("workflow-instance-id"); + if (workflowInstanceId == null || workflowInstanceId.length() == 0) { throw new IllegalArgumentException("Cancel workflow dialog called without workflow instance id"); } + + this.workflowInstance = workflowService.getWorkflowById(workflowInstanceId); + if (this.workflowInstance == null) + { + throw new IllegalArgumentException("Failed to find workflow instance for id: " + workflowInstanceId); + } } @Override @@ -44,13 +51,13 @@ public class CancelWorkflowDialog extends BaseDialogBean throws Exception { if (logger.isDebugEnabled()) - logger.debug("Cancelling workflow with id: " + this.workflowInstanceId); + logger.debug("Cancelling workflow with id: " + this.workflowInstance.id); // cancel the workflow - this.workflowService.cancelWorkflow(this.workflowInstanceId); + this.workflowService.cancelWorkflow(this.workflowInstance.id); if (logger.isDebugEnabled()) - logger.debug("Cancelled workflow with id: " + this.workflowInstanceId); + logger.debug("Cancelled workflow with id: " + this.workflowInstance.id); return outcome; } @@ -67,9 +74,21 @@ public class CancelWorkflowDialog extends BaseDialogBean return false; } + @Override + public String getCancelButtonLabel() + { + return Application.getMessage(FacesContext.getCurrentInstance(), "no"); + } + + @Override + public String getFinishButtonLabel() + { + return Application.getMessage(FacesContext.getCurrentInstance(), "yes"); + } + // ------------------------------------------------------------------------------ // Bean Getters and Setters - + /** * Returns the confirmation to display to the user before deleting the content. * @@ -80,8 +99,13 @@ public class CancelWorkflowDialog extends BaseDialogBean String confirmMsg = Application.getMessage(FacesContext.getCurrentInstance(), "cancel_workflow_confirm"); - return MessageFormat.format(confirmMsg, - new Object[] {this.parameters.get("workflow-instance-name")}); + String workflowLabel = this.workflowInstance.definition.title; + if (this.workflowInstance.description != null && this.workflowInstance.description.length() > 0) + { + workflowLabel = workflowLabel + " (" + this.workflowInstance.description + ")"; + } + + return MessageFormat.format(confirmMsg, new Object[] {workflowLabel}); } /** diff --git a/source/java/org/alfresco/web/bean/workflow/ManageTaskDialog.java b/source/java/org/alfresco/web/bean/workflow/ManageTaskDialog.java index 227b6ca416..2e63eb4630 100644 --- a/source/java/org/alfresco/web/bean/workflow/ManageTaskDialog.java +++ b/source/java/org/alfresco/web/bean/workflow/ManageTaskDialog.java @@ -26,6 +26,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.web.app.AlfrescoNavigationHandler; import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.FacesHelper; import org.alfresco.web.bean.dialog.BaseDialogBean; import org.alfresco.web.bean.repository.MapNode; import org.alfresco.web.bean.repository.Node; @@ -105,16 +106,7 @@ public class ManageTaskDialog extends BaseDialogBean this.workflowInstance = this.task.path.instance; // setup the workflow package for the task - Serializable obj = this.task.properties.get(WorkflowModel.ASSOC_PACKAGE); - // TODO: remove this workaroud where JBPM may return a String and not the NodeRef - if (obj instanceof NodeRef) - { - this.workflowPackage = (NodeRef)obj; - } - else if (obj instanceof String) - { - this.workflowPackage = new NodeRef((String)obj); - } + this.workflowPackage = (NodeRef)this.task.properties.get(WorkflowModel.ASSOC_PACKAGE); if (logger.isDebugEnabled()) { @@ -208,7 +200,7 @@ public class ManageTaskDialog extends BaseDialogBean @Override public String getFinishButtonLabel() { - return Application.getMessage(FacesContext.getCurrentInstance(), "save"); + return Application.getMessage(FacesContext.getCurrentInstance(), "save_changes"); } @Override @@ -251,7 +243,7 @@ public class ManageTaskDialog extends BaseDialogBean String selectedTransition = null; for (WorkflowTransition trans : this.transitions) { - Object result = reqParams.get(CLIENT_ID_PREFIX + trans.title); + Object result = reqParams.get(CLIENT_ID_PREFIX + FacesHelper.makeLegalId(trans.title)); if (result != null) { // this was the button that was pressed @@ -635,7 +627,10 @@ public class ManageTaskDialog extends BaseDialogBean node.addPropertyResolver("displayPath", this.browseBean.resolverDisplayPath); // add a property resolver to indicate whether the item has been completed or not -// node.addPropertyResolver("completed", this.completeResolver); +// node.addPropertyResolver("completed", this.completeResolver); + + // add the id of the task being managed + node.getProperties().put("taskId", this.task.id); this.resources.add(node); } diff --git a/source/java/org/alfresco/web/bean/workflow/ReassignTaskDialog.java b/source/java/org/alfresco/web/bean/workflow/ReassignTaskDialog.java index f543730687..6a9ce7be3b 100644 --- a/source/java/org/alfresco/web/bean/workflow/ReassignTaskDialog.java +++ b/source/java/org/alfresco/web/bean/workflow/ReassignTaskDialog.java @@ -50,7 +50,7 @@ public class ReassignTaskDialog extends BaseDialogBean { super.init(parameters); - this.taskId = this.parameters.get("task-id"); + this.taskId = this.parameters.get("id"); if (this.taskId == null || this.taskId.length() == 0) { throw new IllegalArgumentException("Reassign task dialog called without task id"); @@ -137,12 +137,13 @@ public class ReassignTaskDialog extends BaseDialogBean { tx = Repository.getUserTransaction(context, true); tx.begin(); - + // build xpath to match available User/Person objects NodeRef peopleRef = personService.getPeopleContainer(); // NOTE: see SearcherComponentTest - String xpath = "*[like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "firstName, '%" + contains + "%', false)" + - " or " + "like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "lastName, '%" + contains + "%', false)]"; + String xpath = "*[not(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "userName='guest') and " + + "(like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "firstName, '%" + contains + "%', false)" + + " or " + "like(@" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + "lastName, '%" + contains + "%', false))]"; List nodes = searchService.selectNodes( peopleRef, diff --git a/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java index c8e5d0a086..7425da8575 100644 --- a/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java +++ b/source/java/org/alfresco/web/bean/workflow/StartWorkflowWizard.java @@ -46,6 +46,7 @@ import org.apache.commons.logging.LogFactory; public class StartWorkflowWizard extends BaseWizardBean { protected String selectedWorkflow; + protected String previouslySelectedWorkflow; protected List availableWorkflows; protected Map workflows; protected WorkflowService workflowService; @@ -77,18 +78,13 @@ public class StartWorkflowWizard extends BaseWizardBean this.selectedWorkflow = null; } + this.previouslySelectedWorkflow = null; this.startTaskNode = null; this.resources = null; this.itemsToAdd = null; this.packageItemsToAdd = new ArrayList(); this.isItemBeingAdded = false; - if (this.packageItemsRichList != null) - { - this.packageItemsRichList.setValue(null); - this.packageItemsRichList = null; - } - - // TODO: Does this need to be in a read-only transaction?? + resetRichList(); // add the item the workflow wizard was started on to the list of resources String itemToWorkflowId = this.parameters.get("item-to-workflow"); @@ -110,11 +106,7 @@ public class StartWorkflowWizard extends BaseWizardBean public void restored() { // reset the workflow package rich list so everything gets re-evaluated - if (this.packageItemsRichList != null) - { - this.packageItemsRichList.setValue(null); - this.packageItemsRichList = null; - } + resetRichList(); } @Override @@ -190,13 +182,14 @@ public class StartWorkflowWizard extends BaseWizardBean { String stepName = Application.getWizardManager().getCurrentStepName(); - if ("options".equals(stepName) && this.startTaskNode == null) + if ("options".equals(stepName) && + (this.selectedWorkflow.equals(this.previouslySelectedWorkflow) == false)) { // retrieve the start task for the selected workflow, get the task // definition and create a transient node to allow the property // sheet to collect the required data. - WorkflowDefinition flowDef = this.workflows.get(this.selectedWorkflow); + WorkflowDefinition flowDef = this.workflows.get(this.selectedWorkflow); if (logger.isDebugEnabled()) logger.debug("Selected workflow: "+ flowDef); @@ -211,20 +204,60 @@ public class StartWorkflowWizard extends BaseWizardBean this.startTaskNode = new TransientNode(taskDef.metadata.getName(), "task_" + System.currentTimeMillis(), null); } + + // we also need to reset the resources list so that the actions get re-evaluated + resetRichList(); } return null; } + @Override + public String back() + { + String stepName = Application.getWizardManager().getCurrentStepName(); + + // if we have come back to the "choose-workflow" step remember + // the current workflow selection + if ("choose-workflow".equals(stepName)) + { + this.previouslySelectedWorkflow = this.selectedWorkflow; + } + + return null; + } + @Override public boolean getNextButtonDisabled() { return this.nextButtonDisabled; } + @Override + public String getContainerTitle() + { + String wizTitle = null; + + ResourceBundle bundle = Application.getBundle(FacesContext.getCurrentInstance()); + String stepName = Application.getWizardManager().getCurrentStepName(); + + if ("choose-workflow".equals(stepName) == false && this.selectedWorkflow != null) + { + String titlePattern = bundle.getString("start_named_workflow_wizard"); + WorkflowDefinition workflowDef = this.workflows.get(this.selectedWorkflow); + wizTitle = MessageFormat.format(titlePattern, new Object[] {workflowDef.title}); + } + else + { + wizTitle = bundle.getString("start_workflow_wizard"); + } + + return wizTitle; + } + // ------------------------------------------------------------------------------ // Event Handlers - + /** * Prepares the dialog to allow the user to add an item to the workflow package * @@ -293,7 +326,7 @@ public class StartWorkflowWizard extends BaseWizardBean // reset the rich list so it re-renders this.packageItemsRichList.setValue(null); } - + // ------------------------------------------------------------------------------ // Bean Getters and Setters @@ -465,33 +498,34 @@ public class StartWorkflowWizard extends BaseWizardBean */ public List getStartableWorkflows() { - if (this.availableWorkflows == null) + // NOTE: we don't cache the list of startable workflows as they could get + // updated, in which case we need the latest instance id, they could + // theoretically also get removed. + + this.availableWorkflows = new ArrayList(4); + this.workflows = new HashMap(4); + + List workflowDefs = this.workflowService.getDefinitions(); + for (WorkflowDefinition workflowDef : workflowDefs) { - this.availableWorkflows = new ArrayList(4); - this.workflows = new HashMap(4); - - List workflowDefs = this.workflowService.getDefinitions(); - for (WorkflowDefinition workflowDef : workflowDefs) + String label = workflowDef.title; + if (workflowDef.description != null && workflowDef.description.length() > 0) { - String label = workflowDef.title; - if (workflowDef.description != null && workflowDef.description.length() > 0) - { - label = label + " (" + workflowDef.description + ")"; - } - this.availableWorkflows.add(new SelectItem(workflowDef.id, label)); - this.workflows.put(workflowDef.id, workflowDef); - } - - // set the initial selected workflow to the first in the list, unless there are no - // workflows, in which disable the next button - if (this.availableWorkflows.size() > 0) - { - this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue(); - } - else - { - this.nextButtonDisabled = true; + label = label + " (" + workflowDef.description + ")"; } + this.availableWorkflows.add(new SelectItem(workflowDef.id, label)); + this.workflows.put(workflowDef.id, workflowDef); + } + + // set the initial selected workflow to the first in the list, unless there are no + // workflows, in which disable the next button + if (this.availableWorkflows.size() > 0) + { + this.selectedWorkflow = (String)this.availableWorkflows.get(0).getValue(); + } + else + { + this.nextButtonDisabled = true; } return availableWorkflows; @@ -559,4 +593,19 @@ public class StartWorkflowWizard extends BaseWizardBean { this.workflowService = workflowService; } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Resets the rich list + */ + protected void resetRichList() + { + if (this.packageItemsRichList != null) + { + this.packageItemsRichList.setValue(null); + this.packageItemsRichList = null; + } + } } diff --git a/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java b/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java index 9800da87b5..4e5da783fd 100644 --- a/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java +++ b/source/java/org/alfresco/web/bean/workflow/WorkflowBean.java @@ -196,7 +196,10 @@ public class WorkflowBean } // add the targets for this particular association - params.put(assocQName, (Serializable)targets); + if (targets.size() > 0) + { + params.put(assocQName, (Serializable)targets); + } } return params; @@ -223,19 +226,8 @@ public class WorkflowBean node.getProperties().put("id", task.id); // add the name of the source space (if there is one) - // TODO: remove this workaroud where JBPM may return a String and not the NodeRef - Serializable obj = task.properties.get(WorkflowModel.PROP_CONTEXT); - NodeRef context = null; - if (obj instanceof NodeRef) - { - context = (NodeRef)obj; - } - else if (obj instanceof String) - { - context = new NodeRef((String)obj); - } - - if (context != null) + NodeRef context = (NodeRef)task.properties.get(WorkflowModel.PROP_CONTEXT); + if (context != null && this.nodeService.exists(context)) { String name = Repository.getNameForNode(this.nodeService, context); node.getProperties().put("sourceSpaceName", name); @@ -268,7 +260,6 @@ public class WorkflowBean // add the workflow instance id and name this taks belongs to node.getProperties().put("workflowInstanceId", task.path.instance.id); - node.getProperties().put("workflowInstanceName", task.path.instance.definition.title); } return node; diff --git a/source/java/org/alfresco/web/data/Sort.java b/source/java/org/alfresco/web/data/Sort.java index fce5bcaca2..c561e87d86 100644 --- a/source/java/org/alfresco/web/data/Sort.java +++ b/source/java/org/alfresco/web/data/Sort.java @@ -17,6 +17,7 @@ package org.alfresco.web.data; import java.lang.reflect.Method; +import java.sql.Timestamp; import java.text.CollationKey; import java.text.Collator; import java.util.ArrayList; @@ -175,6 +176,10 @@ public abstract class Sort { this.comparator = new FloatComparator(); } + else if (returnType.equals(Timestamp.class)) + { + this.comparator = new TimestampComparator(); + } else { s_logger.warn("Unsupported sort data type: " + returnType + " defaulting to .toString()"); @@ -408,6 +413,20 @@ public abstract class Sort } } + private static class TimestampComparator implements Comparator + { + /** + * @see org.alfresco.web.data.IDataComparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(final Object obj1, final Object obj2) + { + if (obj1 == null && obj2 == null) return 0; + if (obj1 == null) return -1; + if (obj2 == null) return 1; + return ((Timestamp)obj1).compareTo((Timestamp)obj2); + } + } + // ------------------------------------------------------------------------------ // Private Data diff --git a/source/java/org/alfresco/web/ui/common/Utils.java b/source/java/org/alfresco/web/ui/common/Utils.java index 687db5ecb3..10da2bdc41 100644 --- a/source/java/org/alfresco/web/ui/common/Utils.java +++ b/source/java/org/alfresco/web/ui/common/Utils.java @@ -55,6 +55,7 @@ import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; @@ -968,7 +969,8 @@ public final class Utils if (err != null) { if ((err instanceof InvalidNodeRefException == false && - err instanceof AccessDeniedException == false) || logger.isDebugEnabled()) + err instanceof AccessDeniedException == false && + err instanceof NoTransformerException == false) || logger.isDebugEnabled()) { logger.error(msg, err); } diff --git a/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java index 0d0acb2fc2..78b0f0b863 100644 --- a/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java +++ b/source/java/org/alfresco/web/ui/common/renderer/ActionLinkRenderer.java @@ -111,165 +111,175 @@ public class ActionLinkRenderer extends BaseRenderer */ private String renderActionLink(FacesContext context, UIActionLink link) { - Map attrs = link.getAttributes(); - StringBuilder linkBuf = new StringBuilder(256); + // if there is no value for the link there will be no visible output + // on the page so don't bother rendering anything + String linkHtml = ""; + Object linkValue = link.getValue(); - if (link.getHref() == null) + if (linkValue != null) { - linkBuf.append("'); - - StringBuilder buf = new StringBuilder(350); - if (link.getImage() != null) - { - int padding = link.getPadding(); - if (padding != 0) - { - // TODO: make this width value a property! - buf.append("
"); - } - - if (link.getShowLink() == false) - { - buf.append(linkBuf.toString()); - } - - // TODO: allow configuring of alignment attribute - buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue(), "absmiddle")); - - if (link.getShowLink() == false) - { - buf.append(""); - } - else - { - if (padding != 0) - { - buf.append(""); + linkBuf.append(link.getOnclick()); } else { - // TODO: add horizontal spacing as component property - buf.append(""); + // generate JavaScript to set a hidden form field and submit + // a form which request attributes that we can decode + linkBuf.append(Utils.generateFormSubmit(context, link, Utils.getActionHiddenFieldName(context, link), link.getClientId(context), getParameterComponents(link))); } - buf.append(linkBuf.toString()); - buf.append(Utils.encode(link.getValue().toString())); - buf.append(""); + linkBuf.append('"'); + } + else + { + String href = link.getHref(); - if (padding == 0) + // prefix the web context path if required + linkBuf.append(""); + linkBuf.append(context.getExternalContext().getRequestContextPath()); + } + linkBuf.append(href); + + // append arguments if specified + Map actionParams = getParameterComponents(link); + if (actionParams != null) + { + boolean first = (href.indexOf('?') == -1); + for (String name : actionParams.keySet()) + { + String paramValue = actionParams.get(name); + if (first) + { + linkBuf.append('?'); + first = false; + } + else + { + linkBuf.append('&'); + } + try + { + linkBuf.append(name).append("=").append(URLEncoder.encode(paramValue, "UTF-8")); + } + catch (UnsupportedEncodingException err) + { + // if this happens we have bigger problems than a missing URL parameter...! + } + } + } + + linkBuf.append('"'); + + // output href 'target' attribute if supplied + if (link.getTarget() != null) + { + linkBuf.append(" target=\"") + .append(link.getTarget()) + .append("\""); } } - if (padding != 0) + if (attrs.get("style") != null) { - buf.append("
"); + linkBuf.append(" style=\"") + .append(attrs.get("style")) + .append('"'); } - } - else - { - buf.append(linkBuf.toString()); - buf.append(Utils.encode(link.getValue().toString())); - buf.append("
"); + if (attrs.get("styleClass") != null) + { + linkBuf.append(" class=") + .append(attrs.get("styleClass")); + } + if (link.getTooltip() != null) + { + linkBuf.append(" title=\"") + .append(Utils.encode(link.getTooltip())) + .append('"'); + } + linkBuf.append('>'); + + StringBuilder buf = new StringBuilder(350); + if (link.getImage() != null) + { + int padding = link.getPadding(); + if (padding != 0) + { + // TODO: make this width value a property! + buf.append("
"); + } + + if (link.getShowLink() == false) + { + buf.append(linkBuf.toString()); + } + + // TODO: allow configuring of alignment attribute + buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue(), "absmiddle")); + + if (link.getShowLink() == false) + { + buf.append(""); + } + else + { + if (padding != 0) + { + buf.append(""); + } + else + { + // TODO: add horizontal spacing as component property + buf.append(""); + } + + buf.append(linkBuf.toString()); + buf.append(Utils.encode(link.getValue().toString())); + buf.append(""); + + if (padding == 0) + { + buf.append(""); + } + } + + if (padding != 0) + { + buf.append("
"); + } + } + else + { + buf.append(linkBuf.toString()); + buf.append(Utils.encode(link.getValue().toString())); + buf.append(""); + } + + linkHtml = buf.toString(); } - return buf.toString(); + return linkHtml; } /** @@ -282,71 +292,81 @@ public class ActionLinkRenderer extends BaseRenderer */ private String renderMenuAction(FacesContext context, UIActionLink link, int padding) { - StringBuilder buf = new StringBuilder(256); + // if there is no value for the link there will be no visible output + // on the page so don't bother rendering anything + String linkHtml = ""; + Object linkValue = link.getValue(); - buf.append(""); - - // render image cell first for a menu - if (link.getImage() != null) + if (linkValue != null) { - buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue())); - } - - buf.append(""); - - // render text link cell for the menu - if (link.getHref() == null) - { - buf.append(""); + + // render image cell first for a menu + if (link.getImage() != null) { - buf.append(" target=\"") - .append(link.getTarget()) - .append("\""); + buf.append(Utils.buildImageTag(context, link.getImage(), (String)link.getValue())); } + + buf.append(""); + + // render text link cell for the menu + if (link.getHref() == null) + { + buf.append("'); + buf.append(Utils.encode(link.getValue().toString())); + buf.append(""); + + buf.append(""); + + linkHtml = buf.toString(); } - Map attrs = link.getAttributes(); - if (attrs.get("style") != null) - { - buf.append(" style=\"") - .append(attrs.get("style")) - .append('"'); - } - if (attrs.get("styleClass") != null) - { - buf.append(" class=") - .append(attrs.get("styleClass")); - } - buf.append('>'); - buf.append(Utils.encode(link.getValue().toString())); - buf.append(""); - - buf.append(""); - - return buf.toString(); + return linkHtml; } diff --git a/source/java/org/alfresco/web/ui/common/renderer/DatePickerRenderer.java b/source/java/org/alfresco/web/ui/common/renderer/DatePickerRenderer.java index 6181461f69..d9458bd107 100644 --- a/source/java/org/alfresco/web/ui/common/renderer/DatePickerRenderer.java +++ b/source/java/org/alfresco/web/ui/common/renderer/DatePickerRenderer.java @@ -168,6 +168,7 @@ public class DatePickerRenderer extends BaseRenderer * input component must render the submitted value if it's set, and use the local * value only if there is no submitted value. */ + @SuppressWarnings("deprecation") public void encodeBegin(FacesContext context, UIComponent component) throws IOException { @@ -178,6 +179,7 @@ public class DatePickerRenderer extends BaseRenderer String clientId = component.getClientId(context); ResponseWriter out = context.getResponseWriter(); String cmdFieldName = clientId + FIELD_CMD; + Boolean initIfNull = (Boolean)component.getAttributes().get("initialiseIfNull"); // this is part of the spec: // first you attempt to build the date from the submitted value @@ -188,12 +190,19 @@ public class DatePickerRenderer extends BaseRenderer } else { - // second if no submitted value is found, default to the current value + // second - if no submitted value is found, default to the current value Object value = ((ValueHolder)component).getValue(); if (value instanceof Date) { date = (Date)value; } + + // third - if no date is present and the initialiseIfNull attribute + // is set to true set the date to today's date + if (date == null && initIfNull != null && initIfNull.booleanValue()) + { + date = new Date(); + } } // create a flag to show if the component is disabled @@ -253,18 +262,23 @@ public class DatePickerRenderer extends BaseRenderer out.write(" "); // render 2 links (if the component is not disabled) to allow the user to reset the - // date back to null or to select today's date + // date back to null (if initialiseIfNull is false) or to select today's date if (disabled.booleanValue() == false) { out.write(" "); + out.write("\"> "); + + if (initIfNull != null && initIfNull.booleanValue() == false) + { + out.write(""); + } } } else @@ -412,7 +426,7 @@ public class DatePickerRenderer extends BaseRenderer Locale locale = Application.getLanguage(FacesContext.getCurrentInstance()); if (locale == null) { - locale = locale.getDefault(); + locale = Locale.getDefault(); } DateFormatSymbols dfs = new DateFormatSymbols(locale); String[] names = dfs.getMonths(); diff --git a/source/java/org/alfresco/web/ui/common/tag/InputDatePickerTag.java b/source/java/org/alfresco/web/ui/common/tag/InputDatePickerTag.java index 15e6cb5ffa..ab7b1e41d2 100644 --- a/source/java/org/alfresco/web/ui/common/tag/InputDatePickerTag.java +++ b/source/java/org/alfresco/web/ui/common/tag/InputDatePickerTag.java @@ -60,6 +60,7 @@ public class InputDatePickerTag extends HtmlComponentTag this.value = null; this.showTime = null; this.disabled = null; + this.initIfNull = null; } /** @@ -75,6 +76,7 @@ public class InputDatePickerTag extends HtmlComponentTag setStringProperty(component, "value", this.value); setBooleanProperty(component, "showTime", this.showTime); setBooleanProperty(component, "disabled", this.disabled); + setBooleanProperty(component, "initialiseIfNull", this.initIfNull); } /** @@ -127,9 +129,22 @@ public class InputDatePickerTag extends HtmlComponentTag this.disabled = disabled; } + /** + * Sets whether today's date should be shown initially if the underlying + * model value is null. This will also hide the None button thus disallowing + * the user to set the date back to null. + * + * @param initialiseIfNull true to show today's date instead of 'None' + */ + public void setInitialiseIfNull(String initialiseIfNull) + { + this.initIfNull = initialiseIfNull; + } + private String startYear = null; private String yearCount = null; private String value = null; private String showTime = null; private String disabled = null; + private String initIfNull = null; } diff --git a/source/java/org/alfresco/web/ui/repo/component/UIDialogButtons.java b/source/java/org/alfresco/web/ui/repo/component/UIDialogButtons.java index e9f19c9927..826644455d 100644 --- a/source/java/org/alfresco/web/ui/repo/component/UIDialogButtons.java +++ b/source/java/org/alfresco/web/ui/repo/component/UIDialogButtons.java @@ -186,6 +186,9 @@ public class UIDialogButtons extends SelfRenderingComponent if (logger.isDebugEnabled()) logger.debug("Adding " + buttons.size() + " additional buttons: " + buttons); + // add a spacing row to separate the additional buttons from the OK button + addSpacingRow(context); + for (DialogButtonConfig buttonCfg : buttons) { UICommand button = (UICommand)context.getApplication(). diff --git a/source/java/org/alfresco/web/ui/repo/component/UINodeWorkflowInfo.java b/source/java/org/alfresco/web/ui/repo/component/UINodeWorkflowInfo.java new file mode 100644 index 0000000000..34323c25ed --- /dev/null +++ b/source/java/org/alfresco/web/ui/repo/component/UINodeWorkflowInfo.java @@ -0,0 +1,348 @@ +package org.alfresco.web.ui.repo.component; + +import java.io.IOException; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.faces.el.ValueBinding; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.app.Application; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.ui.common.component.SelfRenderingComponent; + +/** + * JSF component that displays information about the workflows a node is involved in. + *

+ * The node to show workflow information on. + * + * @author gavinc + */ +public class UINodeWorkflowInfo extends SelfRenderingComponent +{ + protected Node value = null; + + // ------------------------------------------------------------------------------ + // Component Impl + + @Override + public String getFamily() + { + return "org.alfresco.faces.NodeWorkflowInfo"; + } + + @Override + public void restoreState(FacesContext context, Object state) + { + Object values[] = (Object[])state; + // standard component attributes are restored by the super class + super.restoreState(context, values[0]); + this.value = (Node)values[1]; + } + + @Override + public Object saveState(FacesContext context) + { + Object values[] = new Object[8]; + // standard component attributes are saved by the super class + values[0] = super.saveState(context); + values[1] = this.value; + return values; + } + + @Override + @SuppressWarnings("unchecked") + public void encodeBegin(FacesContext context) throws IOException + { + if (!isRendered()) return; + + // get the node to display the information for + Node node = getValue(); + + if (node != null) + { + // get the services we need + NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); + DictionaryService ddService = Repository.getServiceRegistry(context).getDictionaryService(); + WorkflowService workflowService = Repository.getServiceRegistry(context).getWorkflowService(); + ResponseWriter out = context.getResponseWriter(); + ResourceBundle bundle = Application.getBundle(context); + + // render simple workflow info + renderSimpleWorkflowInfo(context, node, nodeService, ddService, out, bundle); + + // render advanced workflow info + renderAdvancedWorkflowInfo(context, node, nodeService, ddService, workflowService, out, bundle); + } + } + + @Override + public void encodeEnd(FacesContext context) throws IOException + { + if (!isRendered()) return; + } + + // ------------------------------------------------------------------------------ + // Strongly typed component property accessors + + /** + * Get the value, this will be a node representing a piece of content or a space + * + * @return the value + */ + public Node getValue() + { + ValueBinding vb = getValueBinding("value"); + if (vb != null) + { + this.value = (Node)vb.getValue(getFacesContext()); + } + + return this.value; + } + + /** + * Set the value, either a space or content node. + * + * @param value the value + */ + public void setValue(Node value) + { + this.value = value; + } + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Renders the simple workflow details for the given node. + * + * @param context Faces context + * @param node The node + * @param nodeService The NodeService instance + * @param ddService The Data Dictionary instance + * @param out The response writer + * @param bundle Message bundle to get strings from + */ + protected void renderSimpleWorkflowInfo(FacesContext context, Node node, + NodeService nodeService, DictionaryService ddService, + ResponseWriter out, ResourceBundle bundle) + throws IOException + { + boolean isContent = true; + + QName type = nodeService.getType(node.getNodeRef()); + if (ddService.isSubClass(type, ContentModel.TYPE_FOLDER)) + { + isContent = false; + } + + // Render HTML for simple workflow + if (isContent) + { + // TODO: for now we only support advanced workflow on content so only + // render the simple workflow title if the node is a content node + out.write("

"); + out.write(bundle.getString("simple_workflow")); + out.write("
"); + } + out.write("
"); + + if (node.hasAspect(ContentModel.ASPECT_SIMPLE_WORKFLOW)) + { + // get the simple workflow aspect properties + Map props = node.getProperties(); + + String approveStepName = (String)props.get( + ContentModel.PROP_APPROVE_STEP.toString()); + String rejectStepName = (String)props.get( + ContentModel.PROP_REJECT_STEP.toString()); + + Boolean approveMove = (Boolean)props.get( + ContentModel.PROP_APPROVE_MOVE.toString()); + Boolean rejectMove = (Boolean)props.get( + ContentModel.PROP_REJECT_MOVE.toString()); + + NodeRef approveFolder = (NodeRef)props.get( + ContentModel.PROP_APPROVE_FOLDER.toString()); + NodeRef rejectFolder = (NodeRef)props.get( + ContentModel.PROP_REJECT_FOLDER.toString()); + + String approveFolderName = null; + String rejectFolderName = null; + + // get the approve folder name + if (approveFolder != null) + { + Node approveNode = new Node(approveFolder); + approveFolderName = approveNode.getName(); + } + + // get the reject folder name + if (rejectFolder != null) + { + Node rejectNode = new Node(rejectFolder); + rejectFolderName = rejectNode.getName(); + } + + // calculate the approve action string + String action = null; + if (approveMove.booleanValue()) + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); + } + else + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); + } + + String actionPattern = null; + if (isContent) + { + actionPattern = Application.getMessage(FacesContext.getCurrentInstance(), "document_action"); + } + else + { + actionPattern = Application.getMessage(FacesContext.getCurrentInstance(), "space_action"); + } + Object[] params = new Object[] {action, approveFolderName, approveStepName}; + out.write(MessageFormat.format(actionPattern, params)); + + // add details of the reject step if there is one + if (rejectStepName != null && rejectMove != null && rejectFolderName != null) + { + if (rejectMove.booleanValue()) + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "moved"); + } + else + { + action = Application.getMessage(FacesContext.getCurrentInstance(), "copied"); + } + + out.write(" "); + params = new Object[] {action, rejectFolderName, rejectStepName}; + out.write(MessageFormat.format(actionPattern, params)); + } + } + else + { + // work out which no workflow message to show depending on the node type + if (isContent) + { + out.write(bundle.getString("doc_not_in_simple_workflow")); + } + else + { + out.write(bundle.getString("space_not_in_simple_workflow")); + } + } + out.write("
"); + } + + /** + * Renders the advanced workflow details for the given node. + * + * @param context Faces context + * @param node The node + * @param nodeService The NodeService instance + * @param ddService The Data Dictionary instance + * @param workflowService The WorkflowService instance + * @param out The response writer + * @param bundle Message bundle to get strings from + */ + protected void renderAdvancedWorkflowInfo(FacesContext context, Node node, + NodeService nodeService, DictionaryService ddService, WorkflowService workflowService, + ResponseWriter out, ResourceBundle bundle) + throws IOException + { + boolean isContent = true; + + QName type = nodeService.getType(node.getNodeRef()); + if (ddService.isSubClass(type, ContentModel.TYPE_FOLDER)) + { + isContent = false; + } + + // TODO: for now we only support advanced workflow on content so don't render + // anything for other types + if (isContent) + { + // Render HTML for advanved workflow + out.write("
"); + out.write(bundle.getString("advanced_workflows")); + out.write("
"); + + List workflows = workflowService.getWorkflowsForContent( + node.getNodeRef(), true); + if (workflows != null && workflows.size() > 0) + { + SimpleDateFormat format = new SimpleDateFormat(bundle.getString("date_pattern")); + + // list out all the workflows the document is part of + if (isContent) + { + out.write(bundle.getString("doc_part_of_advanced_workflows")); + } + else + { + out.write(bundle.getString("space_part_of_advanced_workflows")); + } + out.write(":
    "); + for (WorkflowInstance wi : workflows) + { + out.write("
  • "); + out.write(wi.definition.title); + if (wi.description != null && wi.description.length() > 0) + { + out.write(" ("); + out.write(wi.description); + out.write(")"); + } + out.write(" "); + if (wi.startDate != null) + { + out.write(bundle.getString("started_on").toLowerCase()); + out.write(" "); + out.write(format.format(wi.startDate)); + out.write(" "); + } + if (wi.initiator != null) + { + out.write(bundle.getString("by")); + out.write(" "); + out.write(User.getFullName(nodeService, wi.initiator)); + out.write("."); + } + out.write("
  • "); + } + out.write("
"); + } + else + { + if (isContent) + { + out.write(bundle.getString("doc_not_in_advanced_workflow")); + } + else + { + out.write(bundle.getString("space_not_in_advanced_workflow")); + } + } + out.write("
"); + } + } +} diff --git a/source/java/org/alfresco/web/ui/repo/component/UISearchCustomProperties.java b/source/java/org/alfresco/web/ui/repo/component/UISearchCustomProperties.java index fc085b9029..5d461669d8 100644 --- a/source/java/org/alfresco/web/ui/repo/component/UISearchCustomProperties.java +++ b/source/java/org/alfresco/web/ui/repo/component/UISearchCustomProperties.java @@ -319,6 +319,7 @@ public class UISearchCustomProperties extends SelfRenderingComponent implements inputFromDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER); inputFromDate.setValueBinding("startYear", startYearBind); inputFromDate.setValueBinding("yearCount", yearCountBind); + inputFromDate.getAttributes().put("initialiseIfNull", Boolean.TRUE); inputFromDate.getAttributes().put("showTime", showTime); ValueBinding vbFromDate = facesApp.createValueBinding( "#{" + beanBinding + "[\"" + PREFIX_DATE_FROM + propDef.getName().toString() + "\"]}"); @@ -338,6 +339,7 @@ public class UISearchCustomProperties extends SelfRenderingComponent implements inputToDate.setRendererType(RepoConstants.ALFRESCO_FACES_DATE_PICKER_RENDERER); inputToDate.setValueBinding("startYear", startYearBind); inputToDate.setValueBinding("yearCount", yearCountBind); + inputToDate.getAttributes().put("initialiseIfNull", Boolean.TRUE); inputToDate.getAttributes().put("showTime", showTime); ValueBinding vbToDate = facesApp.createValueBinding( "#{" + beanBinding + "[\"" + PREFIX_DATE_TO + propDef.getName().toString() + "\"]}"); diff --git a/source/java/org/alfresco/web/ui/repo/component/UIWorkflowSummary.java b/source/java/org/alfresco/web/ui/repo/component/UIWorkflowSummary.java index 40676f0844..f8aded5c93 100644 --- a/source/java/org/alfresco/web/ui/repo/component/UIWorkflowSummary.java +++ b/source/java/org/alfresco/web/ui/repo/component/UIWorkflowSummary.java @@ -8,11 +8,10 @@ import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.web.app.Application; import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; import org.alfresco.web.ui.common.component.SelfRenderingComponent; /** @@ -87,35 +86,40 @@ public class UIWorkflowSummary extends SelfRenderingComponent out.write(bundle.getString("title")); out.write(":"); out.write(wi.definition.title); - out.write(""); - out.write(bundle.getString("description")); - out.write(":"); - out.write(wi.definition.description); + if (wi.definition.description != null && wi.definition.description.length() > 0) + { + out.write(" ("); + out.write(wi.definition.description); + out.write(")"); + } out.write(""); out.write(bundle.getString("initiated_by")); out.write(":"); if (wi.initiator != null) { - NodeService nodeService = Repository.getServiceRegistry( - context).getNodeService(); - String userName = (String)nodeService.getProperty( - wi.initiator, ContentModel.PROP_USERNAME); - out.write(userName); + out.write(User.getFullName(Repository.getServiceRegistry( + context).getNodeService(), wi.initiator)); } out.write(""); - out.write(bundle.getString("start_date")); + out.write(bundle.getString("started_on")); out.write(":"); if (wi.startDate != null) { out.write(format.format(wi.startDate)); } out.write(""); - out.write(bundle.getString("due_date")); + out.write(bundle.getString("completed_on")); out.write(":"); if (wi.endDate != null) { out.write(format.format(wi.endDate)); } + else + { + out.write("<"); + out.write(bundle.getString("in_progress")); + out.write(">"); + } out.write(""); } } diff --git a/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java index b79bceb239..3cf4ac6590 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/BaseAssociationEditor.java @@ -37,13 +37,16 @@ import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.web.app.Application; import org.alfresco.web.bean.repository.DataDictionary; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; import org.alfresco.web.ui.common.Utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -701,7 +704,7 @@ public abstract class BaseAssociationEditor extends UIInput if (ContentModel.TYPE_PERSON.equals(nodeService.getType(targetRef))) { - out.write((String)nodeService.getProperty(targetRef, ContentModel.PROP_USERNAME)); + out.write(User.getFullName(nodeService, targetRef)); } else { @@ -821,21 +824,29 @@ public abstract class BaseAssociationEditor extends UIInput item.getId().equals(currentNode.getId()) == false) || this.removed.containsKey(item.getId())) { - out.write(""); + } } else { + out.write(""); } - out.write(""); } } } @@ -877,34 +888,52 @@ public abstract class BaseAssociationEditor extends UIInput if (contains != null && contains.length() > 0) { String safeContains = Utils.remove(contains.trim(), "\""); - query.append(" AND +@"); // if the association's target is the person type search on the - // username instead of the name property + // firstName and lastName properties instead of the name property if (type.equals(ContentModel.TYPE_PERSON.toString())) { - String userName = Repository.escapeQName(QName.createQName( - NamespaceService.CONTENT_MODEL_1_0_URI, "userName")); - query.append(userName); + query.append(" AND (@"); + String firstName = Repository.escapeQName(QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, "firstName")); + query.append(firstName); + query.append(":*" + safeContains + "*"); + query.append(" OR @"); + String lastName = Repository.escapeQName(QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, "lastName")); + query.append(lastName); + query.append(":*" + safeContains + "*)"); } else { + query.append(" AND +@"); String nameAttr = Repository.escapeQName(QName.createQName( NamespaceService.CONTENT_MODEL_1_0_URI, "name")); query.append(nameAttr); + query.append(":*" + safeContains + "*"); } - - query.append(":*" + safeContains + "*"); } if (logger.isDebugEnabled()) logger.debug("Query: " + query.toString()); + SearchParameters searchParams = new SearchParameters(); + searchParams.addStore(Repository.getStoreRef()); + searchParams.setLanguage(SearchService.LANGUAGE_LUCENE); + searchParams.setQuery(query.toString()); + + if (type.equals(ContentModel.TYPE_PERSON.toString())) + { + searchParams.addSort("@" + ContentModel.PROP_LASTNAME, true); + + if (logger.isDebugEnabled()) + logger.debug("Added lastname as sort column to query for people"); + } + ResultSet results = null; try { - results = Repository.getServiceRegistry(context).getSearchService().query( - Repository.getStoreRef(), SearchService.LANGUAGE_LUCENE, query.toString()); + results = Repository.getServiceRegistry(context).getSearchService().query(searchParams); this.availableOptions = results.getNodeRefs(); } finally diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java index dbd65eaf0d..169a05cb9f 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIAssociationEditor.java @@ -33,6 +33,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -157,7 +158,7 @@ public class UIAssociationEditor extends BaseAssociationEditor // if the node represents a person, show the username instead of the name if (ContentModel.TYPE_PERSON.equals(nodeService.getType(targetNode))) { - out.write((String)nodeService.getProperty(targetNode, ContentModel.PROP_USERNAME)); + out.write(User.getFullName(nodeService, targetNode)); } else { diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java index 9d693ffc36..4ecc833994 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIChildAssociationEditor.java @@ -33,6 +33,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.web.bean.repository.Node; import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -157,7 +158,7 @@ public class UIChildAssociationEditor extends BaseAssociationEditor // if the node represents a person, show the username instead of the name if (ContentModel.TYPE_PERSON.equals(nodeService.getType(targetNode))) { - out.write((String)nodeService.getProperty(targetNode, ContentModel.PROP_USERNAME)); + out.write(User.getFullName(nodeService, targetNode)); } else { diff --git a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java index d60b492400..97fb700929 100644 --- a/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java +++ b/source/java/org/alfresco/web/ui/repo/component/property/UIPropertySheet.java @@ -75,6 +75,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer private Boolean validationEnabled; private String mode; private String configArea; + private String nextButtonId; private String finishButtonId; /** @@ -219,6 +220,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer this.validationEnabled = (Boolean)values[7]; this.validations = (List)values[8]; this.finishButtonId = (String)values[9]; + this.nextButtonId = (String)values[10]; } /** @@ -226,7 +228,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer */ public Object saveState(FacesContext context) { - Object values[] = new Object[10]; + Object values[] = new Object[11]; // standard component attributes are saved by the super class values[0] = super.saveState(context); values[1] = this.nodeRef; @@ -238,6 +240,7 @@ public class UIPropertySheet extends UIPanel implements NamingContainer values[7] = this.validationEnabled; values[8] = this.validations; values[9] = this.finishButtonId; + values[10] = this.nextButtonId; return (values); } @@ -391,6 +394,26 @@ public class UIPropertySheet extends UIPanel implements NamingContainer { this.finishButtonId = finishButtonId; } + + /** + * Returns the id of the next button + * + * @return The id of the next button on the page + */ + public String getNextButtonId() + { + return this.nextButtonId; + } + + /** + * Sets the id of the next button being used on the page + * + * @param nextButtonId The id of the next button + */ + public void setNextButtonId(String nextButtonId) + { + this.nextButtonId = nextButtonId; + } /** * @return Returns the mode @@ -487,8 +510,12 @@ public class UIPropertySheet extends UIPanel implements NamingContainer ResponseWriter out = context.getResponseWriter(); UIForm form = Utils.getParentForm(context, this); + // TODO: We need to encode all the JavaScript functions here + // with the client id of the property sheet so that we + // can potentially add more than one property sheet to + // page and have validation function correctly. + // output the validation.js script - // TODO: make sure its only included once per page!! out.write("\n + + + + <%-- load a bundle of properties with I18N strings --%> + + + + + <%-- Main outer table --%> + + + <%-- Title bar --%> + + + + + <%-- Main area --%> + + <%-- Shelf --%> + + + <%-- Work Area --%> + + +
+ <%@ include file="../parts/titlebar.jsp" %> +
+ <%@ include file="../parts/shelf.jsp" %> + + + <%-- Breadcrumb --%> + <%@ include file="../parts/breadcrumb.jsp" %> + + <%-- Status and Actions --%> + + + + + + + <%-- separator row with gradient shadow --%> + + + + + + + <%-- Details --%> + + + + + + + <%-- Error Messages --%> + + + + + + + <%-- separator row with bottom panel graphics --%> + + + + + + +
+ + <%-- Status and Actions inner contents table --%> + <%-- Generally this consists of an icon, textual summary and actions for the current object --%> + + + + + +
+ +
+
+
+ +
+ + + + + + +
+ <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "white", "white"); %> + + + + + + + + + + + + + + + + + + + + + + + + + + +
: + +
+ + + + + + + + +
+ + + + + : + +
+
+ + + + +
+ + + + + +
+ + + + + + + + + +
+ :  + +
+ + + + + + + + +
+ + + + + : + +
+
+
+
+ <% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "white"); %> +
+ <% PanelGenerator.generatePanelStart(out, request.getContextPath(), "blue", "#D3E6FE"); %> + + + + + + + + +
+ +
+ +
+ <% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "blue"); %> +
+
+ <%-- messages tag to show messages not handled by other specific message tags --%> + +
+
+ +
+ +
+ + \ No newline at end of file diff --git a/source/web/jsp/dialog/space-details.jsp b/source/web/jsp/dialog/space-details.jsp index 32dc64a698..fe13782bab 100644 --- a/source/web/jsp/dialog/space-details.jsp +++ b/source/web/jsp/dialog/space-details.jsp @@ -161,7 +161,7 @@ - @@ -201,6 +201,23 @@
+ + + + + + + + + + + +
+ diff --git a/source/web/jsp/workflow/manage-task-dialog.jsp b/source/web/jsp/workflow/manage-task-dialog.jsp index f3816e9996..68f8ff50db 100644 --- a/source/web/jsp/workflow/manage-task-dialog.jsp +++ b/source/web/jsp/workflow/manage-task-dialog.jsp @@ -31,14 +31,17 @@ + + + initialSortColumn="name" initialSortDescending="true" + rendered="#{not empty DialogManager.bean.resources}"> <%-- Name column --%> - + @@ -51,7 +54,7 @@ <%-- Description column --%> - + @@ -59,15 +62,16 @@ <%-- Path column --%> - + - + <%-- Created Date column --%> - + @@ -77,7 +81,7 @@ <%-- Modified Date column --%> - + @@ -87,25 +91,13 @@ <%-- Actions column --%> - + - - <%-- Completed column --%> - <%-- - - - - - - - - - --%> diff --git a/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp b/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp index ca5e0873d8..08bd4b827c 100644 --- a/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp +++ b/source/web/jsp/workflow/start-workflow-wizard/workflow-options.jsp @@ -42,7 +42,8 @@ titleBorder="blue" titleBgcolor="#D3E6FE" styleClass="mainSubTitle"> + var="taskProps" columns="1" externalConfig="true" + nextButtonId="next-button" /> @@ -57,7 +58,7 @@ initialSortColumn="name" initialSortDescending="true"> <%-- Name column --%> - + @@ -69,7 +70,7 @@ <%-- Description column --%> - + @@ -77,15 +78,15 @@ <%-- Path column --%> - + - + <%-- Created Date column --%> - + @@ -95,7 +96,7 @@ <%-- Modified Date column --%> - + @@ -105,7 +106,7 @@ <%-- Actions column --%> - + diff --git a/source/web/jsp/workflow/tasks-completed-dashlet.jsp b/source/web/jsp/workflow/tasks-completed-dashlet.jsp index 8b624bc8cf..87b422232c 100644 --- a/source/web/jsp/workflow/tasks-completed-dashlet.jsp +++ b/source/web/jsp/workflow/tasks-completed-dashlet.jsp @@ -3,68 +3,92 @@ <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + + + initialSortColumn="bpm:completionDate" initialSortDescending="true" + rendered="#{not empty WorkflowBean.tasksCompleted}"> <%-- Primary column for details view mode --%> - + - - - - <%-- Task id column --%> - + + <%-- Task type --%> + - + - + - <%-- Source column --%> - + <%-- Task id column --%> + - + - + + + + <%-- Source column --%> + + + + + + + + + + <%-- Created Date column --%> + + + + + + + <%-- Completed date column --%> - + - + - + <%-- Outcome column --%> - + - + - + <%-- Actions column --%> - + - + - diff --git a/source/web/jsp/workflow/tasks-todo-dashlet.jsp b/source/web/jsp/workflow/tasks-todo-dashlet.jsp index 3b1b872932..be2f84d44c 100644 --- a/source/web/jsp/workflow/tasks-todo-dashlet.jsp +++ b/source/web/jsp/workflow/tasks-todo-dashlet.jsp @@ -3,76 +3,100 @@ <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> + + - + initialSortColumn="created" initialSortDescending="true" + rendered="#{not empty WorkflowBean.tasksToDo}"> + <%-- Primary column for details view mode --%> - + - - + + <%-- Task type --%> + + + + + + <%-- Task id column --%> - + - + - + <%-- Source column --%> - + - + - + + + + + + <%-- Created Date column --%> + + + + + + + <%-- Due date column --%> - + - + - + <%-- Status column --%> - + - + - + <%-- Priority column --%> - + - + - + <%-- Actions column --%> - + - + - diff --git a/source/web/jsp/workflow/view-completed-task-dialog.jsp b/source/web/jsp/workflow/view-completed-task-dialog.jsp index e93db93780..3c97d2c8b7 100644 --- a/source/web/jsp/workflow/view-completed-task-dialog.jsp +++ b/source/web/jsp/workflow/view-completed-task-dialog.jsp @@ -31,14 +31,17 @@ + + + initialSortColumn="name" initialSortDescending="true" + rendered="#{not empty DialogManager.bean.resources}"> <%-- Name column --%> - + @@ -51,7 +54,7 @@ <%-- Description column --%> - + @@ -59,15 +62,16 @@ <%-- Path column --%> - + - + <%-- Created Date column --%> - + @@ -77,7 +81,7 @@ <%-- Modified Date column --%> - + diff --git a/source/web/scripts/ajax/dojo.js b/source/web/scripts/ajax/dojo.js new file mode 100644 index 0000000000..038afe584f --- /dev/null +++ b/source/web/scripts/ajax/dojo.js @@ -0,0 +1,5593 @@ +/* + Copyright (c) 2004-2006, The Dojo Foundation + All Rights Reserved. + + Licensed under the Academic Free License version 2.1 or above OR the + modified BSD license. For more information on Dojo licensing, see: + + http://dojotoolkit.org/community/licensing.shtml +*/ + +/* + This is a compiled version of Dojo, built for deployment and not for + development. To get an editable version, please visit: + + http://dojotoolkit.org + + for documentation and information on getting the source. +*/ + +if(typeof dojo=="undefined"){ +var dj_global=this; +function dj_undef(_1,_2){ +if(_2==null){ +_2=dj_global; +} +return (typeof _2[_1]=="undefined"); +} +if(dj_undef("djConfig")){ +var djConfig={}; +} +if(dj_undef("dojo")){ +var dojo={}; +} +dojo.version={major:0,minor:3,patch:1,flag:"",revision:Number("$Rev: 4342 $".match(/[0-9]+/)[0]),toString:function(){ +with(dojo.version){ +return major+"."+minor+"."+patch+flag+" ("+revision+")"; +} +}}; +dojo.evalProp=function(_3,_4,_5){ +return (_4&&!dj_undef(_3,_4)?_4[_3]:(_5?(_4[_3]={}):undefined)); +}; +dojo.parseObjPath=function(_6,_7,_8){ +var _9=(_7!=null?_7:dj_global); +var _a=_6.split("."); +var _b=_a.pop(); +for(var i=0,l=_a.length;i1){ +dh.modulesLoadedListeners.push(function(){ +obj[_3d](); +}); +} +} +if(dh.post_load_&&dh.inFlightCount==0&&!dh.loadNotifying){ +dh.callLoaded(); +} +}; +dojo.addOnUnload=function(obj,_40){ +var dh=dojo.hostenv; +if(arguments.length==1){ +dh.unloadListeners.push(obj); +}else{ +if(arguments.length>1){ +dh.unloadListeners.push(function(){ +obj[_40](); +}); +} +} +}; +dojo.hostenv.modulesLoaded=function(){ +if(this.post_load_){ +return; +} +if((this.loadUriStack.length==0)&&(this.getTextStack.length==0)){ +if(this.inFlightCount>0){ +dojo.debug("files still in flight!"); +return; +} +dojo.hostenv.callLoaded(); +} +}; +dojo.hostenv.callLoaded=function(){ +if(typeof setTimeout=="object"){ +setTimeout("dojo.hostenv.loaded();",0); +}else{ +dojo.hostenv.loaded(); +} +}; +dojo.hostenv.getModuleSymbols=function(_42){ +var _43=_42.split("."); +for(var i=_43.length-1;i>0;i--){ +var _45=_43.slice(0,i).join("."); +var _46=this.getModulePrefix(_45); +if(_46!=_45){ +_43.splice(0,i,_46); +break; +} +} +return _43; +}; +dojo.hostenv._global_omit_module_check=false; +dojo.hostenv.loadModule=function(_47,_48,_49){ +if(!_47){ +return; +} +_49=this._global_omit_module_check||_49; +var _4a=this.findModule(_47,false); +if(_4a){ +return _4a; +} +if(dj_undef(_47,this.loading_modules_)){ +this.addedToLoadingCount.push(_47); +} +this.loading_modules_[_47]=1; +var _4b=_47.replace(/\./g,"/")+".js"; +var _4c=this.getModuleSymbols(_47); +var _4d=((_4c[0].charAt(0)!="/")&&(!_4c[0].match(/^\w+:/))); +var _4e=_4c[_4c.length-1]; +var _4f=_47.split("."); +if(_4e=="*"){ +_47=(_4f.slice(0,-1)).join("."); +while(_4c.length){ +_4c.pop(); +_4c.push(this.pkgFileName); +_4b=_4c.join("/")+".js"; +if(_4d&&(_4b.charAt(0)=="/")){ +_4b=_4b.slice(1); +} +ok=this.loadPath(_4b,((!_49)?_47:null)); +if(ok){ +break; +} +_4c.pop(); +} +}else{ +_4b=_4c.join("/")+".js"; +_47=_4f.join("."); +var ok=this.loadPath(_4b,((!_49)?_47:null)); +if((!ok)&&(!_48)){ +_4c.pop(); +while(_4c.length){ +_4b=_4c.join("/")+".js"; +ok=this.loadPath(_4b,((!_49)?_47:null)); +if(ok){ +break; +} +_4c.pop(); +_4b=_4c.join("/")+"/"+this.pkgFileName+".js"; +if(_4d&&(_4b.charAt(0)=="/")){ +_4b=_4b.slice(1); +} +ok=this.loadPath(_4b,((!_49)?_47:null)); +if(ok){ +break; +} +} +} +if((!ok)&&(!_49)){ +dojo.raise("Could not load '"+_47+"'; last tried '"+_4b+"'"); +} +} +if(!_49&&!this["isXDomain"]){ +_4a=this.findModule(_47,false); +if(!_4a){ +dojo.raise("symbol '"+_47+"' is not defined after loading '"+_4b+"'"); +} +} +return _4a; +}; +dojo.hostenv.startPackage=function(_51){ +var _52=dojo.evalObjPath((_51.split(".").slice(0,-1)).join(".")); +this.loaded_modules_[(new String(_51)).toLowerCase()]=_52; +var _53=_51.split(/\./); +if(_53[_53.length-1]=="*"){ +_53.pop(); +} +return dojo.evalObjPath(_53.join("."),true); +}; +dojo.hostenv.findModule=function(_54,_55){ +var lmn=(new String(_54)).toLowerCase(); +if(this.loaded_modules_[lmn]){ +return this.loaded_modules_[lmn]; +} +var _57=dojo.evalObjPath(_54); +if((_54)&&(typeof _57!="undefined")&&(_57)){ +this.loaded_modules_[lmn]=_57; +return _57; +} +if(_55){ +dojo.raise("no loaded module named '"+_54+"'"); +} +return null; +}; +dojo.kwCompoundRequire=function(_58){ +var _59=_58["common"]||[]; +var _5a=(_58[dojo.hostenv.name_])?_59.concat(_58[dojo.hostenv.name_]||[]):_59.concat(_58["default"]||[]); +for(var x=0;x<_5a.length;x++){ +var _5c=_5a[x]; +if(_5c.constructor==Array){ +dojo.hostenv.loadModule.apply(dojo.hostenv,_5c); +}else{ +dojo.hostenv.loadModule(_5c); +} +} +}; +dojo.require=function(){ +dojo.hostenv.loadModule.apply(dojo.hostenv,arguments); +}; +dojo.requireIf=function(){ +if((arguments[0]===true)||(arguments[0]=="common")||(arguments[0]&&dojo.render[arguments[0]].capable)){ +var _5d=[]; +for(var i=1;i1){ +var _67=_66[1]; +var _68=_67.split("&"); +for(var x in _68){ +var sp=_68[x].split("="); +if((sp[0].length>9)&&(sp[0].substr(0,9)=="djConfig.")){ +var opt=sp[0].substr(9); +try{ +djConfig[opt]=eval(sp[1]); +} +catch(e){ +djConfig[opt]=sp[1]; +} +} +} +} +} +if(((djConfig["baseScriptUri"]=="")||(djConfig["baseRelativePath"]==""))&&(document&&document.getElementsByTagName)){ +var _6c=document.getElementsByTagName("script"); +var _6d=/(__package__|dojo|bootstrap1)\.js([\?\.]|$)/i; +for(var i=0;i<_6c.length;i++){ +var src=_6c[i].getAttribute("src"); +if(!src){ +continue; +} +var m=src.match(_6d); +if(m){ +var _71=src.substring(0,m.index); +if(src.indexOf("bootstrap1")>-1){ +_71+="../"; +} +if(!this["djConfig"]){ +djConfig={}; +} +if(djConfig["baseScriptUri"]==""){ +djConfig["baseScriptUri"]=_71; +} +if(djConfig["baseRelativePath"]==""){ +djConfig["baseRelativePath"]=_71; +} +break; +} +} +} +var dr=dojo.render; +var drh=dojo.render.html; +var drs=dojo.render.svg; +var dua=drh.UA=navigator.userAgent; +var dav=drh.AV=navigator.appVersion; +var t=true; +var f=false; +drh.capable=t; +drh.support.builtin=t; +dr.ver=parseFloat(drh.AV); +dr.os.mac=dav.indexOf("Macintosh")>=0; +dr.os.win=dav.indexOf("Windows")>=0; +dr.os.linux=dav.indexOf("X11")>=0; +drh.opera=dua.indexOf("Opera")>=0; +drh.khtml=(dav.indexOf("Konqueror")>=0)||(dav.indexOf("Safari")>=0); +drh.safari=dav.indexOf("Safari")>=0; +var _79=dua.indexOf("Gecko"); +drh.mozilla=drh.moz=(_79>=0)&&(!drh.khtml); +if(drh.mozilla){ +drh.geckoVersion=dua.substring(_79+6,_79+14); +} +drh.ie=(document.all)&&(!drh.opera); +drh.ie50=drh.ie&&dav.indexOf("MSIE 5.0")>=0; +drh.ie55=drh.ie&&dav.indexOf("MSIE 5.5")>=0; +drh.ie60=drh.ie&&dav.indexOf("MSIE 6.0")>=0; +drh.ie70=drh.ie&&dav.indexOf("MSIE 7.0")>=0; +dojo.locale=(drh.ie?navigator.userLanguage:navigator.language).toLowerCase(); +dr.vml.capable=drh.ie; +drs.capable=f; +drs.support.plugin=f; +drs.support.builtin=f; +if(document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("org.w3c.dom.svg","1.0")){ +drs.capable=t; +drs.support.builtin=t; +drs.support.plugin=f; +} +})(); +dojo.hostenv.startPackage("dojo.hostenv"); +dojo.render.name=dojo.hostenv.name_="browser"; +dojo.hostenv.searchIds=[]; +dojo.hostenv._XMLHTTP_PROGIDS=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"]; +dojo.hostenv.getXmlhttpObject=function(){ +var _7a=null; +var _7b=null; +try{ +_7a=new XMLHttpRequest(); +} +catch(e){ +} +if(!_7a){ +for(var i=0;i<3;++i){ +var _7d=dojo.hostenv._XMLHTTP_PROGIDS[i]; +try{ +_7a=new ActiveXObject(_7d); +} +catch(e){ +_7b=e; +} +if(_7a){ +dojo.hostenv._XMLHTTP_PROGIDS=[_7d]; +break; +} +} +} +if(!_7a){ +return dojo.raise("XMLHTTP not available",_7b); +} +return _7a; +}; +dojo.hostenv.getText=function(uri,_7f,_80){ +var _81=this.getXmlhttpObject(); +if(_7f){ +_81.onreadystatechange=function(){ +if(4==_81.readyState){ +if((!_81["status"])||((200<=_81.status)&&(300>_81.status))){ +_7f(_81.responseText); +} +} +}; +} +_81.open("GET",uri,_7f?true:false); +try{ +_81.send(null); +if(_7f){ +return null; +} +if((_81["status"])&&((200>_81.status)||(300<=_81.status))){ +throw Error("Unable to load "+uri+" status:"+_81.status); +} +} +catch(e){ +if((_80)&&(!_7f)){ +return null; +}else{ +throw e; +} +} +return _81.responseText; +}; +dojo.hostenv.defaultDebugContainerId="dojoDebug"; +dojo.hostenv._println_buffer=[]; +dojo.hostenv._println_safe=false; +dojo.hostenv.println=function(_82){ +if(!dojo.hostenv._println_safe){ +dojo.hostenv._println_buffer.push(_82); +}else{ +try{ +var _83=document.getElementById(djConfig.debugContainerId?djConfig.debugContainerId:dojo.hostenv.defaultDebugContainerId); +if(!_83){ +_83=document.getElementsByTagName("body")[0]||document.body; +} +var div=document.createElement("div"); +div.appendChild(document.createTextNode(_82)); +_83.appendChild(div); +} +catch(e){ +try{ +document.write("
"+_82+"
"); +} +catch(e2){ +window.status=_82; +} +} +} +}; +dojo.addOnLoad(function(){ +dojo.hostenv._println_safe=true; +while(dojo.hostenv._println_buffer.length>0){ +dojo.hostenv.println(dojo.hostenv._println_buffer.shift()); +} +}); +function dj_addNodeEvtHdlr(_85,_86,fp,_88){ +var _89=_85["on"+_86]||function(){ +}; +_85["on"+_86]=function(){ +fp.apply(_85,arguments); +_89.apply(_85,arguments); +}; +return true; +} +dj_addNodeEvtHdlr(window,"load",function(){ +if(arguments.callee.initialized){ +return; +} +arguments.callee.initialized=true; +var _8a=function(){ +if(dojo.render.html.ie){ +dojo.hostenv.makeWidgets(); +} +}; +if(dojo.hostenv.inFlightCount==0){ +_8a(); +dojo.hostenv.modulesLoaded(); +}else{ +dojo.addOnLoad(_8a); +} +}); +dj_addNodeEvtHdlr(window,"unload",function(){ +dojo.hostenv.unloaded(); +}); +dojo.hostenv.makeWidgets=function(){ +var _8b=[]; +if(djConfig.searchIds&&djConfig.searchIds.length>0){ +_8b=_8b.concat(djConfig.searchIds); +} +if(dojo.hostenv.searchIds&&dojo.hostenv.searchIds.length>0){ +_8b=_8b.concat(dojo.hostenv.searchIds); +} +if((djConfig.parseWidgets)||(_8b.length>0)){ +if(dojo.evalObjPath("dojo.widget.Parse")){ +var _8c=new dojo.xml.Parse(); +if(_8b.length>0){ +for(var x=0;x<_8b.length;x++){ +var _8e=document.getElementById(_8b[x]); +if(!_8e){ +continue; +} +var _8f=_8c.parseElement(_8e,null,true); +dojo.widget.getParser().createComponents(_8f); +} +}else{ +if(djConfig.parseWidgets){ +var _8f=_8c.parseElement(document.getElementsByTagName("body")[0]||document.body,null,true); +dojo.widget.getParser().createComponents(_8f); +} +} +} +} +}; +dojo.addOnLoad(function(){ +if(!dojo.render.html.ie){ +dojo.hostenv.makeWidgets(); +} +}); +try{ +if(dojo.render.html.ie){ +document.write(""); +document.write(""); +} +} +catch(e){ +} +dojo.hostenv.writeIncludes=function(){ +}; +dojo.byId=function(id,doc){ +if(id&&(typeof id=="string"||id instanceof String)){ +if(!doc){ +doc=document; +} +return doc.getElementById(id); +} +return id; +}; +(function(){ +if(typeof dj_usingBootstrap!="undefined"){ +return; +} +var _92=false; +var _93=false; +var _94=false; +if((typeof this["load"]=="function")&&((typeof this["Packages"]=="function")||(typeof this["Packages"]=="object"))){ +_92=true; +}else{ +if(typeof this["load"]=="function"){ +_93=true; +}else{ +if(window.widget){ +_94=true; +} +} +} +var _95=[]; +if((this["djConfig"])&&((djConfig["isDebug"])||(djConfig["debugAtAllCosts"]))){ +_95.push("debug.js"); +} +if((this["djConfig"])&&(djConfig["debugAtAllCosts"])&&(!_92)&&(!_94)){ +_95.push("browser_debug.js"); +} +if((this["djConfig"])&&(djConfig["compat"])){ +_95.push("compat/"+djConfig["compat"]+".js"); +} +var _96=djConfig["baseScriptUri"]; +if((this["djConfig"])&&(djConfig["baseLoaderUri"])){ +_96=djConfig["baseLoaderUri"]; +} +for(var x=0;x<_95.length;x++){ +var _98=_96+"src/"+_95[x]; +if(_92||_93){ +load(_98); +}else{ +try{ +document.write(""); +} +catch(e){ +var _99=document.createElement("script"); +_99.src=_98; +document.getElementsByTagName("head")[0].appendChild(_99); +} +} +} +})(); +dojo.fallback_locale="en"; +dojo.normalizeLocale=function(_9a){ +return _9a?_9a.toLowerCase():dojo.locale; +}; +dojo.requireLocalization=function(_9b,_9c,_9d){ +dojo.debug("EXPERIMENTAL: dojo.requireLocalization"); +var _9e=dojo.hostenv.getModuleSymbols(_9b); +var _9f=_9e.concat("nls").join("/"); +_9d=dojo.normalizeLocale(_9d); +var _a0=_9d.split("-"); +var _a1=[]; +for(var i=_a0.length;i>0;i--){ +_a1.push(_a0.slice(0,i).join("-")); +} +if(_a1[_a1.length-1]!=dojo.fallback_locale){ +_a1.push(dojo.fallback_locale); +} +var _a3=[_9b,"_nls",_9c].join("."); +var _a4=dojo.hostenv.startPackage(_a3); +dojo.hostenv.loaded_modules_[_a3]=_a4; +var _a5=false; +for(var i=_a1.length-1;i>=0;i--){ +var loc=_a1[i]; +var pkg=[_a3,loc].join("."); +var _a8=false; +if(!dojo.hostenv.findModule(pkg)){ +dojo.hostenv.loaded_modules_[pkg]=null; +var _a9=[_9f,loc,_9c].join("/")+".js"; +_a8=dojo.hostenv.loadPath(_a9,null,function(_aa){ +_a4[loc]=_aa; +if(_a5){ +for(var x in _a5){ +if(!_a4[loc][x]){ +_a4[loc][x]=_a5[x]; +} +} +} +}); +}else{ +_a8=true; +} +if(_a8&&_a4[loc]){ +_a5=_a4[loc]; +} +} +}; +dojo.provide("dojo.string.common"); +dojo.require("dojo.string"); +dojo.string.trim=function(str,wh){ +if(!str.replace){ +return str; +} +if(!str.length){ +return str; +} +var re=(wh>0)?(/^\s+/):(wh<0)?(/\s+$/):(/^\s+|\s+$/g); +return str.replace(re,""); +}; +dojo.string.trimStart=function(str){ +return dojo.string.trim(str,1); +}; +dojo.string.trimEnd=function(str){ +return dojo.string.trim(str,-1); +}; +dojo.string.repeat=function(str,_b2,_b3){ +var out=""; +for(var i=0;i<_b2;i++){ +out+=str; +if(_b3&&i<_b2-1){ +out+=_b3; +} +} +return out; +}; +dojo.string.pad=function(str,len,c,dir){ +var out=String(str); +if(!c){ +c="0"; +} +if(!dir){ +dir=1; +} +while(out.length0){ +out=c+out; +}else{ +out+=c; +} +} +return out; +}; +dojo.string.padLeft=function(str,len,c){ +return dojo.string.pad(str,len,c,1); +}; +dojo.string.padRight=function(str,len,c){ +return dojo.string.pad(str,len,c,-1); +}; +dojo.provide("dojo.string"); +dojo.require("dojo.string.common"); +dojo.provide("dojo.lang.common"); +dojo.require("dojo.lang"); +dojo.lang._mixin=function(obj,_c2){ +var _c3={}; +for(var x in _c2){ +if(typeof _c3[x]=="undefined"||_c3[x]!=_c2[x]){ +obj[x]=_c2[x]; +} +} +if(dojo.render.html.ie&&dojo.lang.isFunction(_c2["toString"])&&_c2["toString"]!=obj["toString"]){ +obj.toString=_c2.toString; +} +return obj; +}; +dojo.lang.mixin=function(obj,_c6){ +for(var i=1,l=arguments.length;i-1; +}; +dojo.lang.isObject=function(wh){ +if(typeof wh=="undefined"){ +return false; +} +return (typeof wh=="object"||wh===null||dojo.lang.isArray(wh)||dojo.lang.isFunction(wh)); +}; +dojo.lang.isArray=function(wh){ +return (wh instanceof Array||typeof wh=="array"); +}; +dojo.lang.isArrayLike=function(wh){ +if(dojo.lang.isString(wh)){ +return false; +} +if(dojo.lang.isFunction(wh)){ +return false; +} +if(dojo.lang.isArray(wh)){ +return true; +} +if(typeof wh!="undefined"&&wh&&dojo.lang.isNumber(wh.length)&&isFinite(wh.length)){ +return true; +} +return false; +}; +dojo.lang.isFunction=function(wh){ +if(!wh){ +return false; +} +return (wh instanceof Function||typeof wh=="function"); +}; +dojo.lang.isString=function(wh){ +return (wh instanceof String||typeof wh=="string"); +}; +dojo.lang.isAlien=function(wh){ +if(!wh){ +return false; +} +return !dojo.lang.isFunction()&&/\{\s*\[native code\]\s*\}/.test(String(wh)); +}; +dojo.lang.isBoolean=function(wh){ +return (wh instanceof Boolean||typeof wh=="boolean"); +}; +dojo.lang.isNumber=function(wh){ +return (wh instanceof Number||typeof wh=="number"); +}; +dojo.lang.isUndefined=function(wh){ +return ((wh==undefined)&&(typeof wh=="undefined")); +}; +dojo.provide("dojo.lang.extras"); +dojo.require("dojo.lang.common"); +dojo.lang.setTimeout=function(_e2,_e3){ +var _e4=window,argsStart=2; +if(!dojo.lang.isFunction(_e2)){ +_e4=_e2; +_e2=_e3; +_e3=arguments[2]; +argsStart++; +} +if(dojo.lang.isString(_e2)){ +_e2=_e4[_e2]; +} +var _e5=[]; +for(var i=argsStart;i=4){ +this.changeUrl=_f7; +} +} +}; +dojo.lang.extend(dojo.io.Request,{url:"",mimetype:"text/plain",method:"GET",content:undefined,transport:undefined,changeUrl:undefined,formNode:undefined,sync:false,bindSuccess:false,useCache:false,preventCache:false,load:function(_f8,_f9,evt){ +},error:function(_fb,_fc){ +},timeout:function(_fd){ +},handle:function(){ +},timeoutSeconds:0,abort:function(){ +},fromKwArgs:function(_fe){ +if(_fe["url"]){ +_fe.url=_fe.url.toString(); +} +if(_fe["formNode"]){ +_fe.formNode=dojo.byId(_fe.formNode); +} +if(!_fe["method"]&&_fe["formNode"]&&_fe["formNode"].method){ +_fe.method=_fe["formNode"].method; +} +if(!_fe["handle"]&&_fe["handler"]){ +_fe.handle=_fe.handler; +} +if(!_fe["load"]&&_fe["loaded"]){ +_fe.load=_fe.loaded; +} +if(!_fe["changeUrl"]&&_fe["changeURL"]){ +_fe.changeUrl=_fe.changeURL; +} +_fe.encoding=dojo.lang.firstValued(_fe["encoding"],djConfig["bindEncoding"],""); +_fe.sendTransport=dojo.lang.firstValued(_fe["sendTransport"],djConfig["ioSendTransport"],false); +var _ff=dojo.lang.isFunction; +for(var x=0;x0){ +dojo.io.bind(dojo.io._bindQueue.shift()); +}else{ +dojo.io._queueBindInFlight=false; +} +} +}; +dojo.io._bindQueue=[]; +dojo.io._queueBindInFlight=false; +dojo.io.argsFromMap=function(map,_110,last){ +var enc=/utf/i.test(_110||"")?encodeURIComponent:dojo.string.encodeAscii; +var _113=[]; +var _114=new Object(); +for(var name in map){ +var _116=function(elt){ +var val=enc(name)+"="+enc(elt); +_113[(last==name)?"push":"unshift"](val); +}; +if(!_114[name]){ +var _119=map[name]; +if(dojo.lang.isArray(_119)){ +dojo.lang.forEach(_119,_116); +}else{ +_116(_119); +} +} +} +return _113.join("&"); +}; +dojo.io.setIFrameSrc=function(_11a,src,_11c){ +try{ +var r=dojo.render.html; +if(!_11c){ +if(r.safari){ +_11a.location=src; +}else{ +frames[_11a.name].location=src; +} +}else{ +var idoc; +if(r.ie){ +idoc=_11a.contentWindow.document; +}else{ +if(r.safari){ +idoc=_11a.document; +}else{ +idoc=_11a.contentWindow; +} +} +if(!idoc){ +_11a.location=src; +return; +}else{ +idoc.location.replace(src); +} +} +} +catch(e){ +dojo.debug(e); +dojo.debug("setIFrameSrc: "+e); +} +}; +dojo.provide("dojo.lang.array"); +dojo.require("dojo.lang.common"); +dojo.lang.has=function(obj,name){ +try{ +return (typeof obj[name]!="undefined"); +} +catch(e){ +return false; +} +}; +dojo.lang.isEmpty=function(obj){ +if(dojo.lang.isObject(obj)){ +var tmp={}; +var _123=0; +for(var x in obj){ +if(obj[x]&&(!tmp[x])){ +_123++; +break; +} +} +return (_123==0); +}else{ +if(dojo.lang.isArrayLike(obj)||dojo.lang.isString(obj)){ +return obj.length==0; +} +} +}; +dojo.lang.map=function(arr,obj,_127){ +var _128=dojo.lang.isString(arr); +if(_128){ +arr=arr.split(""); +} +if(dojo.lang.isFunction(obj)&&(!_127)){ +_127=obj; +obj=dj_global; +}else{ +if(dojo.lang.isFunction(obj)&&_127){ +var _129=obj; +obj=_127; +_127=_129; +} +} +if(Array.map){ +var _12a=Array.map(arr,_127,obj); +}else{ +var _12a=[]; +for(var i=0;i=3){ +dojo.raise("thisObject doesn't exist!"); +} +_13e=dj_global; +} +var _140=[]; +for(var i=0;i/gm,">").replace(/"/gm,"""); +if(!_183){ +str=str.replace(/'/gm,"'"); +} +return str; +}; +dojo.string.escapeSql=function(str){ +return str.replace(/'/gm,"''"); +}; +dojo.string.escapeRegExp=function(str){ +return str.replace(/\\/gm,"\\\\").replace(/([\f\b\n\t\r[\^$|?*+(){}])/gm,"\\$1"); +}; +dojo.string.escapeJavaScript=function(str){ +return str.replace(/(["'\f\b\n\t\r])/gm,"\\$1"); +}; +dojo.string.escapeString=function(str){ +return ("\""+str.replace(/(["\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r"); +}; +dojo.string.summary=function(str,len){ +if(!len||str.length<=len){ +return str; +}else{ +return str.substring(0,len).replace(/\.+$/,"")+"..."; +} +}; +dojo.string.endsWith=function(str,end,_18c){ +if(_18c){ +str=str.toLowerCase(); +end=end.toLowerCase(); +} +if((str.length-end.length)<0){ +return false; +} +return str.lastIndexOf(end)==str.length-end.length; +}; +dojo.string.endsWithAny=function(str){ +for(var i=1;i-1){ +return true; +} +} +return false; +}; +dojo.string.normalizeNewlines=function(text,_197){ +if(_197=="\n"){ +text=text.replace(/\r\n/g,"\n"); +text=text.replace(/\r/g,"\n"); +}else{ +if(_197=="\r"){ +text=text.replace(/\r\n/g,"\r"); +text=text.replace(/\n/g,"\r"); +}else{ +text=text.replace(/([^\r])\n/g,"$1\r\n"); +text=text.replace(/\r([^\n])/g,"\r\n$1"); +} +} +return text; +}; +dojo.string.splitEscaped=function(str,_199){ +var _19a=[]; +for(var i=0,prevcomma=0;i5)&&(_1a1[x].indexOf("dojo-")>=0)){ +return "dojo:"+_1a1[x].substr(5).toLowerCase(); +} +} +} +} +} +return _19e.toLowerCase(); +}; +dojo.dom.getUniqueId=function(){ +do{ +var id="dj_unique_"+(++arguments.callee._idIncrement); +}while(document.getElementById(id)); +return id; +}; +dojo.dom.getUniqueId._idIncrement=0; +dojo.dom.firstElement=dojo.dom.getFirstChildElement=function(_1a4,_1a5){ +var node=_1a4.firstChild; +while(node&&node.nodeType!=dojo.dom.ELEMENT_NODE){ +node=node.nextSibling; +} +if(_1a5&&node&&node.tagName&&node.tagName.toLowerCase()!=_1a5.toLowerCase()){ +node=dojo.dom.nextElement(node,_1a5); +} +return node; +}; +dojo.dom.lastElement=dojo.dom.getLastChildElement=function(_1a7,_1a8){ +var node=_1a7.lastChild; +while(node&&node.nodeType!=dojo.dom.ELEMENT_NODE){ +node=node.previousSibling; +} +if(_1a8&&node&&node.tagName&&node.tagName.toLowerCase()!=_1a8.toLowerCase()){ +node=dojo.dom.prevElement(node,_1a8); +} +return node; +}; +dojo.dom.nextElement=dojo.dom.getNextSiblingElement=function(node,_1ab){ +if(!node){ +return null; +} +do{ +node=node.nextSibling; +}while(node&&node.nodeType!=dojo.dom.ELEMENT_NODE); +if(node&&_1ab&&_1ab.toLowerCase()!=node.tagName.toLowerCase()){ +return dojo.dom.nextElement(node,_1ab); +} +return node; +}; +dojo.dom.prevElement=dojo.dom.getPreviousSiblingElement=function(node,_1ad){ +if(!node){ +return null; +} +if(_1ad){ +_1ad=_1ad.toLowerCase(); +} +do{ +node=node.previousSibling; +}while(node&&node.nodeType!=dojo.dom.ELEMENT_NODE); +if(node&&_1ad&&_1ad.toLowerCase()!=node.tagName.toLowerCase()){ +return dojo.dom.prevElement(node,_1ad); +} +return node; +}; +dojo.dom.moveChildren=function(_1ae,_1af,trim){ +var _1b1=0; +if(trim){ +while(_1ae.hasChildNodes()&&_1ae.firstChild.nodeType==dojo.dom.TEXT_NODE){ +_1ae.removeChild(_1ae.firstChild); +} +while(_1ae.hasChildNodes()&&_1ae.lastChild.nodeType==dojo.dom.TEXT_NODE){ +_1ae.removeChild(_1ae.lastChild); +} +} +while(_1ae.hasChildNodes()){ +_1af.appendChild(_1ae.firstChild); +_1b1++; +} +return _1b1; +}; +dojo.dom.copyChildren=function(_1b2,_1b3,trim){ +var _1b5=_1b2.cloneNode(true); +return this.moveChildren(_1b5,_1b3,trim); +}; +dojo.dom.removeChildren=function(node){ +var _1b7=node.childNodes.length; +while(node.hasChildNodes()){ +node.removeChild(node.firstChild); +} +return _1b7; +}; +dojo.dom.replaceChildren=function(node,_1b9){ +dojo.dom.removeChildren(node); +node.appendChild(_1b9); +}; +dojo.dom.removeNode=function(node){ +if(node&&node.parentNode){ +return node.parentNode.removeChild(node); +} +}; +dojo.dom.getAncestors=function(node,_1bc,_1bd){ +var _1be=[]; +var _1bf=dojo.lang.isFunction(_1bc); +while(node){ +if(!_1bf||_1bc(node)){ +_1be.push(node); +} +if(_1bd&&_1be.length>0){ +return _1be[0]; +} +node=node.parentNode; +} +if(_1bd){ +return null; +} +return _1be; +}; +dojo.dom.getAncestorsByTag=function(node,tag,_1c2){ +tag=tag.toLowerCase(); +return dojo.dom.getAncestors(node,function(el){ +return ((el.tagName)&&(el.tagName.toLowerCase()==tag)); +},_1c2); +}; +dojo.dom.getFirstAncestorByTag=function(node,tag){ +return dojo.dom.getAncestorsByTag(node,tag,true); +}; +dojo.dom.isDescendantOf=function(node,_1c7,_1c8){ +if(_1c8&&node){ +node=node.parentNode; +} +while(node){ +if(node==_1c7){ +return true; +} +node=node.parentNode; +} +return false; +}; +dojo.dom.innerXML=function(node){ +if(node.innerXML){ +return node.innerXML; +}else{ +if(node.xml){ +return node.xml; +}else{ +if(typeof XMLSerializer!="undefined"){ +return (new XMLSerializer()).serializeToString(node); +} +} +} +}; +dojo.dom.createDocument=function(){ +var doc=null; +if(!dj_undef("ActiveXObject")){ +var _1cb=["MSXML2","Microsoft","MSXML","MSXML3"]; +for(var i=0;i<_1cb.length;i++){ +try{ +doc=new ActiveXObject(_1cb[i]+".XMLDOM"); +} +catch(e){ +} +if(doc){ +break; +} +} +}else{ +if((document.implementation)&&(document.implementation.createDocument)){ +doc=document.implementation.createDocument("","",null); +} +} +return doc; +}; +dojo.dom.createDocumentFromText=function(str,_1ce){ +if(!_1ce){ +_1ce="text/xml"; +} +if(!dj_undef("DOMParser")){ +var _1cf=new DOMParser(); +return _1cf.parseFromString(str,_1ce); +}else{ +if(!dj_undef("ActiveXObject")){ +var _1d0=dojo.dom.createDocument(); +if(_1d0){ +_1d0.async=false; +_1d0.loadXML(str); +return _1d0; +}else{ +dojo.debug("toXml didn't work?"); +} +}else{ +if(document.createElement){ +var tmp=document.createElement("xml"); +tmp.innerHTML=str; +if(document.implementation&&document.implementation.createDocument){ +var _1d2=document.implementation.createDocument("foo","",null); +for(var i=0;i"); +} +} +catch(e){ +} +if(dojo.render.html.opera){ +dojo.debug("Opera is not supported with dojo.undo.browser, so back/forward detection will not work."); +} +dojo.undo.browser={initialHref:window.location.href,initialHash:window.location.hash,moveForward:false,historyStack:[],forwardStack:[],historyIframe:null,bookmarkAnchor:null,locationTimer:null,setInitialState:function(args){ +this.initialState={"url":this.initialHref,"kwArgs":args,"urlHash":this.initialHash}; +},addToHistory:function(args){ +var hash=null; +if(!this.historyIframe){ +this.historyIframe=window.frames["djhistory"]; +} +if(!this.bookmarkAnchor){ +this.bookmarkAnchor=document.createElement("a"); +(document.body||document.getElementsByTagName("body")[0]).appendChild(this.bookmarkAnchor); +this.bookmarkAnchor.style.display="none"; +} +if((!args["changeUrl"])||(dojo.render.html.ie)){ +var url=dojo.hostenv.getBaseScriptUri()+"iframe_history.html?"+(new Date()).getTime(); +this.moveForward=true; +dojo.io.setIFrameSrc(this.historyIframe,url,false); +} +if(args["changeUrl"]){ +this.changingUrl=true; +hash="#"+((args["changeUrl"]!==true)?args["changeUrl"]:(new Date()).getTime()); +setTimeout("window.location.href = '"+hash+"'; dojo.undo.browser.changingUrl = false;",1); +this.bookmarkAnchor.href=hash; +if(dojo.render.html.ie){ +var _1f4=args["back"]||args["backButton"]||args["handle"]; +var tcb=function(_1f6){ +if(window.location.hash!=""){ +setTimeout("window.location.href = '"+hash+"';",1); +} +_1f4.apply(this,[_1f6]); +}; +if(args["back"]){ +args.back=tcb; +}else{ +if(args["backButton"]){ +args.backButton=tcb; +}else{ +if(args["handle"]){ +args.handle=tcb; +} +} +} +this.forwardStack=[]; +var _1f7=args["forward"]||args["forwardButton"]||args["handle"]; +var tfw=function(_1f9){ +if(window.location.hash!=""){ +window.location.href=hash; +} +if(_1f7){ +_1f7.apply(this,[_1f9]); +} +}; +if(args["forward"]){ +args.forward=tfw; +}else{ +if(args["forwardButton"]){ +args.forwardButton=tfw; +}else{ +if(args["handle"]){ +args.handle=tfw; +} +} +} +}else{ +if(dojo.render.html.moz){ +if(!this.locationTimer){ +this.locationTimer=setInterval("dojo.undo.browser.checkLocation();",200); +} +} +} +} +this.historyStack.push({"url":url,"kwArgs":args,"urlHash":hash}); +},checkLocation:function(){ +if(!this.changingUrl){ +var hsl=this.historyStack.length; +if((window.location.hash==this.initialHash||window.location.href==this.initialHref)&&(hsl==1)){ +this.handleBackButton(); +return; +} +if(this.forwardStack.length>0){ +if(this.forwardStack[this.forwardStack.length-1].urlHash==window.location.hash){ +this.handleForwardButton(); +return; +} +} +if((hsl>=2)&&(this.historyStack[hsl-2])){ +if(this.historyStack[hsl-2].urlHash==window.location.hash){ +this.handleBackButton(); +return; +} +} +} +},iframeLoaded:function(evt,_1fc){ +if(!dojo.render.html.opera){ +var _1fd=this._getUrlQuery(_1fc.href); +if(_1fd==null){ +if(this.historyStack.length==1){ +this.handleBackButton(); +} +return; +} +if(this.moveForward){ +this.moveForward=false; +return; +} +if(this.historyStack.length>=2&&_1fd==this._getUrlQuery(this.historyStack[this.historyStack.length-2].url)){ +this.handleBackButton(); +}else{ +if(this.forwardStack.length>0&&_1fd==this._getUrlQuery(this.forwardStack[this.forwardStack.length-1].url)){ +this.handleForwardButton(); +} +} +} +},handleBackButton:function(){ +var _1fe=this.historyStack.pop(); +if(!_1fe){ +return; +} +var last=this.historyStack[this.historyStack.length-1]; +if(!last&&this.historyStack.length==0){ +last=this.initialState; +} +if(last){ +if(last.kwArgs["back"]){ +last.kwArgs["back"](); +}else{ +if(last.kwArgs["backButton"]){ +last.kwArgs["backButton"](); +}else{ +if(last.kwArgs["handle"]){ +last.kwArgs.handle("back"); +} +} +} +} +this.forwardStack.push(_1fe); +},handleForwardButton:function(){ +var last=this.forwardStack.pop(); +if(!last){ +return; +} +if(last.kwArgs["forward"]){ +last.kwArgs.forward(); +}else{ +if(last.kwArgs["forwardButton"]){ +last.kwArgs.forwardButton(); +}else{ +if(last.kwArgs["handle"]){ +last.kwArgs.handle("forward"); +} +} +} +this.historyStack.push(last); +},_getUrlQuery:function(url){ +var _202=url.split("?"); +if(_202.length<2){ +return null; +}else{ +return _202[1]; +} +}}; +dojo.provide("dojo.io.BrowserIO"); +dojo.require("dojo.io"); +dojo.require("dojo.lang.array"); +dojo.require("dojo.lang.func"); +dojo.require("dojo.string.extras"); +dojo.require("dojo.dom"); +dojo.require("dojo.undo.browser"); +dojo.io.checkChildrenForFile=function(node){ +var _204=false; +var _205=node.getElementsByTagName("input"); +dojo.lang.forEach(_205,function(_206){ +if(_204){ +return; +} +if(_206.getAttribute("type")=="file"){ +_204=true; +} +}); +return _204; +}; +dojo.io.formHasFile=function(_207){ +return dojo.io.checkChildrenForFile(_207); +}; +dojo.io.updateNode=function(node,_209){ +node=dojo.byId(node); +var args=_209; +if(dojo.lang.isString(_209)){ +args={url:_209}; +} +args.mimetype="text/html"; +args.load=function(t,d,e){ +while(node.firstChild){ +if(dojo["event"]){ +try{ +dojo.event.browser.clean(node.firstChild); +} +catch(e){ +} +} +node.removeChild(node.firstChild); +} +node.innerHTML=d; +}; +dojo.io.bind(args); +}; +dojo.io.formFilter=function(node){ +var type=(node.type||"").toLowerCase(); +return !node.disabled&&node.name&&!dojo.lang.inArray(type,["file","submit","image","reset","button"]); +}; +dojo.io.encodeForm=function(_210,_211,_212){ +if((!_210)||(!_210.tagName)||(!_210.tagName.toLowerCase()=="form")){ +dojo.raise("Attempted to encode a non-form element."); +} +if(!_212){ +_212=dojo.io.formFilter; +} +var enc=/utf/i.test(_211||"")?encodeURIComponent:dojo.string.encodeAscii; +var _214=[]; +for(var i=0;i<_210.elements.length;i++){ +var elm=_210.elements[i]; +if(!elm||elm.tagName.toLowerCase()=="fieldset"||!_212(elm)){ +continue; +} +var name=enc(elm.name); +var type=elm.type.toLowerCase(); +if(type=="select-multiple"){ +for(var j=0;j=200)&&(http.status<300))||(http.status==304)||(location.protocol=="file:"&&(http.status==0||http.status==undefined))||(location.protocol=="chrome:"&&(http.status==0||http.status==undefined))){ +var ret; +if(_23b.method.toLowerCase()=="head"){ +var _241=http.getAllResponseHeaders(); +ret={}; +ret.toString=function(){ +return _241; +}; +var _242=_241.split(/[\r\n]+/g); +for(var i=0;i<_242.length;i++){ +var pair=_242[i].match(/^([^:]+)\s*:\s*(.+)$/i); +if(pair){ +ret[pair[1]]=pair[2]; +} +} +}else{ +if(_23b.mimetype=="text/javascript"){ +try{ +ret=dj_eval(http.responseText); +} +catch(e){ +dojo.debug(e); +dojo.debug(http.responseText); +ret=null; +} +}else{ +if(_23b.mimetype=="text/json"){ +try{ +ret=dj_eval("("+http.responseText+")"); +} +catch(e){ +dojo.debug(e); +dojo.debug(http.responseText); +ret=false; +} +}else{ +if((_23b.mimetype=="application/xml")||(_23b.mimetype=="text/xml")){ +ret=http.responseXML; +if(!ret||typeof ret=="string"||!http.getResponseHeader("Content-Type")){ +ret=dojo.dom.createDocumentFromText(http.responseText); +} +}else{ +ret=http.responseText; +} +} +} +} +if(_23f){ +addToCache(url,_23e,_23b.method,http); +} +_23b[(typeof _23b.load=="function")?"load":"handle"]("load",ret,http,_23b); +}else{ +var _245=new dojo.io.Error("XMLHttpTransport Error: "+http.status+" "+http.statusText); +_23b[(typeof _23b.error=="function")?"error":"handle"]("error",_245,http,_23b); +} +} +function setHeaders(http,_247){ +if(_247["headers"]){ +for(var _248 in _247["headers"]){ +if(_248.toLowerCase()=="content-type"&&!_247["contentType"]){ +_247["contentType"]=_247["headers"][_248]; +}else{ +http.setRequestHeader(_248,_247["headers"][_248]); +} +} +} +} +this.inFlight=[]; +this.inFlightTimer=null; +this.startWatchingInFlight=function(){ +if(!this.inFlightTimer){ +this.inFlightTimer=setInterval("dojo.io.XMLHTTPTransport.watchInFlight();",10); +} +}; +this.watchInFlight=function(){ +var now=null; +for(var x=this.inFlight.length-1;x>=0;x--){ +var tif=this.inFlight[x]; +if(!tif){ +this.inFlight.splice(x,1); +continue; +} +if(4==tif.http.readyState){ +this.inFlight.splice(x,1); +doLoad(tif.req,tif.http,tif.url,tif.query,tif.useCache); +}else{ +if(tif.startTime){ +if(!now){ +now=(new Date()).getTime(); +} +if(tif.startTime+(tif.req.timeoutSeconds*1000)-1){ +dojo.debug("Warning: dojo.io.bind: stripping hash values from url:",url); +url=url.split("#")[0]; +} +if(_24e["file"]){ +_24e.method="post"; +} +if(!_24e["method"]){ +_24e.method="get"; +} +if(_24e.method.toLowerCase()=="get"){ +_24e.multipart=false; +}else{ +if(_24e["file"]){ +_24e.multipart=true; +}else{ +if(!_24e["multipart"]){ +_24e.multipart=false; +} +} +} +if(_24e["backButton"]||_24e["back"]||_24e["changeUrl"]){ +dojo.undo.browser.addToHistory(_24e); +} +var _253=_24e["content"]||{}; +if(_24e.sendTransport){ +_253["dojo.transport"]="xmlhttp"; +} +do{ +if(_24e.postContent){ +_250=_24e.postContent; +break; +} +if(_253){ +_250+=dojo.io.argsFromMap(_253,_24e.encoding); +} +if(_24e.method.toLowerCase()=="get"||!_24e.multipart){ +break; +} +var t=[]; +if(_250.length){ +var q=_250.split("&"); +for(var i=0;i-1?"&":"?")+_250; +} +if(_25a){ +_260+=(dojo.string.endsWithAny(_260,"?","&")?"":(_260.indexOf("?")>-1?"&":"?"))+"dojo.preventCache="+new Date().valueOf(); +} +http.open(_24e.method.toUpperCase(),_260,_259); +setHeaders(http,_24e); +try{ +http.send(null); +} +catch(e){ +if(typeof http.abort=="function"){ +http.abort(); +} +doLoad(_24e,{status:404},url,_250,_25b); +} +} +if(!_259){ +doLoad(_24e,http,url,_250,_25b); +} +_24e.abort=function(){ +return http.abort(); +}; +return; +}; +dojo.io.transports.addTransport("XMLHTTPTransport"); +}; +dojo.provide("dojo.event"); +dojo.require("dojo.lang.array"); +dojo.require("dojo.lang.extras"); +dojo.require("dojo.lang.func"); +dojo.event=new function(){ +this.canTimeout=dojo.lang.isFunction(dj_global["setTimeout"])||dojo.lang.isAlien(dj_global["setTimeout"]); +function interpolateArgs(args,_262){ +var dl=dojo.lang; +var ao={srcObj:dj_global,srcFunc:null,adviceObj:dj_global,adviceFunc:null,aroundObj:null,aroundFunc:null,adviceType:(args.length>2)?args[0]:"after",precedence:"last",once:false,delay:null,rate:0,adviceMsg:false}; +switch(args.length){ +case 0: +return; +case 1: +return; +case 2: +ao.srcFunc=args[0]; +ao.adviceFunc=args[1]; +break; +case 3: +if((dl.isObject(args[0]))&&(dl.isString(args[1]))&&(dl.isString(args[2]))){ +ao.adviceType="after"; +ao.srcObj=args[0]; +ao.srcFunc=args[1]; +ao.adviceFunc=args[2]; +}else{ +if((dl.isString(args[1]))&&(dl.isString(args[2]))){ +ao.srcFunc=args[1]; +ao.adviceFunc=args[2]; +}else{ +if((dl.isObject(args[0]))&&(dl.isString(args[1]))&&(dl.isFunction(args[2]))){ +ao.adviceType="after"; +ao.srcObj=args[0]; +ao.srcFunc=args[1]; +var _265=dl.nameAnonFunc(args[2],ao.adviceObj,_262); +ao.adviceFunc=_265; +}else{ +if((dl.isFunction(args[0]))&&(dl.isObject(args[1]))&&(dl.isString(args[2]))){ +ao.adviceType="after"; +ao.srcObj=dj_global; +var _265=dl.nameAnonFunc(args[0],ao.srcObj,_262); +ao.srcFunc=_265; +ao.adviceObj=args[1]; +ao.adviceFunc=args[2]; +} +} +} +} +break; +case 4: +if((dl.isObject(args[0]))&&(dl.isObject(args[2]))){ +ao.adviceType="after"; +ao.srcObj=args[0]; +ao.srcFunc=args[1]; +ao.adviceObj=args[2]; +ao.adviceFunc=args[3]; +}else{ +if((dl.isString(args[0]))&&(dl.isString(args[1]))&&(dl.isObject(args[2]))){ +ao.adviceType=args[0]; +ao.srcObj=dj_global; +ao.srcFunc=args[1]; +ao.adviceObj=args[2]; +ao.adviceFunc=args[3]; +}else{ +if((dl.isString(args[0]))&&(dl.isFunction(args[1]))&&(dl.isObject(args[2]))){ +ao.adviceType=args[0]; +ao.srcObj=dj_global; +var _265=dl.nameAnonFunc(args[1],dj_global,_262); +ao.srcFunc=_265; +ao.adviceObj=args[2]; +ao.adviceFunc=args[3]; +}else{ +if((dl.isString(args[0]))&&(dl.isObject(args[1]))&&(dl.isString(args[2]))&&(dl.isFunction(args[3]))){ +ao.srcObj=args[1]; +ao.srcFunc=args[2]; +var _265=dl.nameAnonFunc(args[3],dj_global,_262); +ao.adviceObj=dj_global; +ao.adviceFunc=_265; +}else{ +if(dl.isObject(args[1])){ +ao.srcObj=args[1]; +ao.srcFunc=args[2]; +ao.adviceObj=dj_global; +ao.adviceFunc=args[3]; +}else{ +if(dl.isObject(args[2])){ +ao.srcObj=dj_global; +ao.srcFunc=args[1]; +ao.adviceObj=args[2]; +ao.adviceFunc=args[3]; +}else{ +ao.srcObj=ao.adviceObj=ao.aroundObj=dj_global; +ao.srcFunc=args[1]; +ao.adviceFunc=args[2]; +ao.aroundFunc=args[3]; +} +} +} +} +} +} +break; +case 6: +ao.srcObj=args[1]; +ao.srcFunc=args[2]; +ao.adviceObj=args[3]; +ao.adviceFunc=args[4]; +ao.aroundFunc=args[5]; +ao.aroundObj=dj_global; +break; +default: +ao.srcObj=args[1]; +ao.srcFunc=args[2]; +ao.adviceObj=args[3]; +ao.adviceFunc=args[4]; +ao.aroundObj=args[5]; +ao.aroundFunc=args[6]; +ao.once=args[7]; +ao.delay=args[8]; +ao.rate=args[9]; +ao.adviceMsg=args[10]; +break; +} +if(dl.isFunction(ao.aroundFunc)){ +var _265=dl.nameAnonFunc(ao.aroundFunc,ao.aroundObj,_262); +ao.aroundFunc=_265; +} +if(dl.isFunction(ao.srcFunc)){ +ao.srcFunc=dl.getNameInObj(ao.srcObj,ao.srcFunc); +} +if(dl.isFunction(ao.adviceFunc)){ +ao.adviceFunc=dl.getNameInObj(ao.adviceObj,ao.adviceFunc); +} +if((ao.aroundObj)&&(dl.isFunction(ao.aroundFunc))){ +ao.aroundFunc=dl.getNameInObj(ao.aroundObj,ao.aroundFunc); +} +if(!ao.srcObj){ +dojo.raise("bad srcObj for srcFunc: "+ao.srcFunc); +} +if(!ao.adviceObj){ +dojo.raise("bad adviceObj for adviceFunc: "+ao.adviceFunc); +} +return ao; +} +this.connect=function(){ +if(arguments.length==1){ +var ao=arguments[0]; +}else{ +var ao=interpolateArgs(arguments,true); +} +if(dojo.lang.isArray(ao.srcObj)&&ao.srcObj!=""){ +var _267={}; +for(var x in ao){ +_267[x]=ao[x]; +} +var mjps=[]; +dojo.lang.forEach(ao.srcObj,function(src){ +if((dojo.render.html.capable)&&(dojo.lang.isString(src))){ +src=dojo.byId(src); +} +_267.srcObj=src; +mjps.push(dojo.event.connect.call(dojo.event,_267)); +}); +return mjps; +} +var mjp=dojo.event.MethodJoinPoint.getForMethod(ao.srcObj,ao.srcFunc); +if(ao.adviceFunc){ +var mjp2=dojo.event.MethodJoinPoint.getForMethod(ao.adviceObj,ao.adviceFunc); +} +mjp.kwAddAdvice(ao); +return mjp; +}; +this.log=function(a1,a2){ +var _26f; +if((arguments.length==1)&&(typeof a1=="object")){ +_26f=a1; +}else{ +_26f={srcObj:a1,srcFunc:a2}; +} +_26f.adviceFunc=function(){ +var _270=[]; +for(var x=0;x=this.jp_.around.length){ +return this.jp_.object[this.jp_.methodname].apply(this.jp_.object,this.args); +}else{ +var ti=this.jp_.around[this.around_index]; +var mobj=ti[0]||dj_global; +var meth=ti[1]; +return mobj[meth].call(mobj,this); +} +}; +dojo.event.MethodJoinPoint=function(obj,_287){ +this.object=obj||dj_global; +this.methodname=_287; +this.methodfunc=this.object[_287]; +this.before=[]; +this.after=[]; +this.around=[]; +}; +dojo.event.MethodJoinPoint.getForMethod=function(obj,_289){ +if(!obj){ +obj=dj_global; +} +if(!obj[_289]){ +obj[_289]=function(){ +}; +if(!obj[_289]){ +dojo.raise("Cannot set do-nothing method on that object "+_289); +} +}else{ +if((!dojo.lang.isFunction(obj[_289]))&&(!dojo.lang.isAlien(obj[_289]))){ +return null; +} +} +var _28a=_289+"$joinpoint"; +var _28b=_289+"$joinpoint$method"; +var _28c=obj[_28a]; +if(!_28c){ +var _28d=false; +if(dojo.event["browser"]){ +if((obj["attachEvent"])||(obj["nodeType"])||(obj["addEventListener"])){ +_28d=true; +dojo.event.browser.addClobberNodeAttrs(obj,[_28a,_28b,_289]); +} +} +var _28e=obj[_289].length; +obj[_28b]=obj[_289]; +_28c=obj[_28a]=new dojo.event.MethodJoinPoint(obj,_28b); +obj[_289]=function(){ +var args=[]; +if((_28d)&&(!arguments.length)){ +var evt=null; +try{ +if(obj.ownerDocument){ +evt=obj.ownerDocument.parentWindow.event; +}else{ +if(obj.documentElement){ +evt=obj.documentElement.ownerDocument.parentWindow.event; +}else{ +evt=window.event; +} +} +} +catch(e){ +evt=window.event; +} +if(evt){ +args.push(dojo.event.browser.fixEvent(evt,this)); +} +}else{ +for(var x=0;x0){ +dojo.lang.forEach(this.before,_296); +} +var _2a6; +if(this.around.length>0){ +var mi=new dojo.event.MethodInvocation(this,obj,args); +_2a6=mi.proceed(); +}else{ +if(this.methodfunc){ +_2a6=this.object[this.methodname].apply(this.object,args); +} +} +if(this.after.length>0){ +dojo.lang.forEach(this.after,_296); +} +return (this.methodfunc)?_2a6:null; +},getArr:function(kind){ +var arr=this.after; +if((typeof kind=="string")&&(kind.indexOf("before")!=-1)){ +arr=this.before; +}else{ +if(kind=="around"){ +arr=this.around; +} +} +return arr; +},kwAddAdvice:function(args){ +this.addAdvice(args["adviceObj"],args["adviceFunc"],args["aroundObj"],args["aroundFunc"],args["adviceType"],args["precedence"],args["once"],args["delay"],args["rate"],args["adviceMsg"]); +},addAdvice:function(_2ab,_2ac,_2ad,_2ae,_2af,_2b0,once,_2b2,rate,_2b4){ +var arr=this.getArr(_2af); +if(!arr){ +dojo.raise("bad this: "+this); +} +var ao=[_2ab,_2ac,_2ad,_2ae,_2b2,rate,_2b4]; +if(once){ +if(this.hasAdvice(_2ab,_2ac,_2af,arr)>=0){ +return; +} +} +if(_2b0=="first"){ +arr.unshift(ao); +}else{ +arr.push(ao); +} +},hasAdvice:function(_2b7,_2b8,_2b9,arr){ +if(!arr){ +arr=this.getArr(_2b9); +} +var ind=-1; +for(var x=0;x=0;i=i-1){ +var el=na[i]; +if(el["__clobberAttrs__"]){ +for(var j=0;j0){ +this.duration=_319; +} +if(_31c){ +this.repeatCount=_31c; +} +if(rate){ +this.rate=rate; +} +if(_318){ +this.handler=_318.handler; +this.beforeBegin=_318.beforeBegin; +this.onBegin=_318.onBegin; +this.onEnd=_318.onEnd; +this.onPlay=_318.onPlay; +this.onPause=_318.onPause; +this.onStop=_318.onStop; +this.onAnimate=_318.onAnimate; +} +if(_31b&&dojo.lang.isFunction(_31b)){ +this.easing=_31b; +} +}; +dojo.inherits(dojo.lfx.Animation,dojo.lfx.IAnimation); +dojo.lang.extend(dojo.lfx.Animation,{_startTime:null,_endTime:null,_timer:null,_percent:0,_startRepeatCount:0,play:function(_31e,_31f){ +if(_31f){ +clearTimeout(this._timer); +this._active=false; +this._paused=false; +this._percent=0; +}else{ +if(this._active&&!this._paused){ +return this; +} +} +this.fire("handler",["beforeBegin"]); +this.fire("beforeBegin"); +if(_31e>0){ +setTimeout(dojo.lang.hitch(this,function(){ +this.play(null,_31f); +}),_31e); +return this; +} +this._startTime=new Date().valueOf(); +if(this._paused){ +this._startTime-=(this.duration*this._percent/100); +} +this._endTime=this._startTime+this.duration; +this._active=true; +this._paused=false; +var step=this._percent/100; +var _321=this.curve.getValue(step); +if(this._percent==0){ +if(!this._startRepeatCount){ +this._startRepeatCount=this.repeatCount; +} +this.fire("handler",["begin",_321]); +this.fire("onBegin",[_321]); +} +this.fire("handler",["play",_321]); +this.fire("onPlay",[_321]); +this._cycle(); +return this; +},pause:function(){ +clearTimeout(this._timer); +if(!this._active){ +return this; +} +this._paused=true; +var _322=this.curve.getValue(this._percent/100); +this.fire("handler",["pause",_322]); +this.fire("onPause",[_322]); +return this; +},gotoPercent:function(pct,_324){ +clearTimeout(this._timer); +this._active=true; +this._paused=true; +this._percent=pct; +if(_324){ +this.play(); +} +},stop:function(_325){ +clearTimeout(this._timer); +var step=this._percent/100; +if(_325){ +step=1; +} +var _327=this.curve.getValue(step); +this.fire("handler",["stop",_327]); +this.fire("onStop",[_327]); +this._active=false; +this._paused=false; +return this; +},status:function(){ +if(this._active){ +return this._paused?"paused":"playing"; +}else{ +return "stopped"; +} +},_cycle:function(){ +clearTimeout(this._timer); +if(this._active){ +var curr=new Date().valueOf(); +var step=(curr-this._startTime)/(this._endTime-this._startTime); +if(step>=1){ +step=1; +this._percent=100; +}else{ +this._percent=step*100; +} +if((this.easing)&&(dojo.lang.isFunction(this.easing))){ +step=this.easing(step); +} +var _32a=this.curve.getValue(step); +this.fire("handler",["animate",_32a]); +this.fire("onAnimate",[_32a]); +if(step<1){ +this._timer=setTimeout(dojo.lang.hitch(this,"_cycle"),this.rate); +}else{ +this._active=false; +this.fire("handler",["end"]); +this.fire("onEnd"); +if(this.repeatCount>0){ +this.repeatCount--; +this.play(null,true); +}else{ +if(this.repeatCount==-1){ +this.play(null,true); +}else{ +if(this._startRepeatCount){ +this.repeatCount=this._startRepeatCount; +this._startRepeatCount=0; +} +} +} +} +} +return this; +}}); +dojo.lfx.Combine=function(){ +dojo.lfx.IAnimation.call(this); +this._anims=[]; +this._animsEnded=0; +var _32b=arguments; +if(_32b.length==1&&(dojo.lang.isArray(_32b[0])||dojo.lang.isArrayLike(_32b[0]))){ +_32b=_32b[0]; +} +var _32c=this; +dojo.lang.forEach(_32b,function(anim){ +_32c._anims.push(anim); +var _32e=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_32e(); +_32c._onAnimsEnded(); +}; +}); +}; +dojo.inherits(dojo.lfx.Combine,dojo.lfx.IAnimation); +dojo.lang.extend(dojo.lfx.Combine,{_animsEnded:0,play:function(_32f,_330){ +if(!this._anims.length){ +return this; +} +this.fire("beforeBegin"); +if(_32f>0){ +setTimeout(dojo.lang.hitch(this,function(){ +this.play(null,_330); +}),_32f); +return this; +} +if(_330||this._anims[0].percent==0){ +this.fire("onBegin"); +} +this.fire("onPlay"); +this._animsCall("play",null,_330); +return this; +},pause:function(){ +this.fire("onPause"); +this._animsCall("pause"); +return this; +},stop:function(_331){ +this.fire("onStop"); +this._animsCall("stop",_331); +return this; +},_onAnimsEnded:function(){ +this._animsEnded++; +if(this._animsEnded>=this._anims.length){ +this.fire("onEnd"); +} +return this; +},_animsCall:function(_332){ +var args=[]; +if(arguments.length>1){ +for(var i=1;i0){ +setTimeout(dojo.lang.hitch(this,function(){ +this.play(null,_33e); +}),_33d); +return this; +} +if(_33f){ +if(this._currAnim==0){ +this.fire("handler",["begin",this._currAnim]); +this.fire("onBegin",[this._currAnim]); +} +this.fire("onPlay",[this._currAnim]); +_33f.play(null,_33e); +} +return this; +},pause:function(){ +if(this._anims[this._currAnim]){ +this._anims[this._currAnim].pause(); +this.fire("onPause",[this._currAnim]); +} +return this; +},playPause:function(){ +if(this._anims.length==0){ +return this; +} +if(this._currAnim==-1){ +this._currAnim=0; +} +var _340=this._anims[this._currAnim]; +if(_340){ +if(!_340._active||_340._paused){ +this.play(); +}else{ +this.pause(); +} +} +return this; +},stop:function(){ +var _341=this._anims[this._currAnim]; +if(_341){ +_341.stop(); +this.fire("onStop",[this._currAnim]); +} +return _341; +},_playNext:function(){ +if(this._currAnim==-1||this._anims.length==0){ +return this; +} +this._currAnim++; +if(this._anims[this._currAnim]){ +this._anims[this._currAnim].play(null,true); +} +return this; +}}); +dojo.lfx.combine=function(){ +var _342=arguments; +if(dojo.lang.isArray(arguments[0])){ +_342=arguments[0]; +} +return new dojo.lfx.Combine(_342); +}; +dojo.lfx.chain=function(){ +var _343=arguments; +if(dojo.lang.isArray(arguments[0])){ +_343=arguments[0]; +} +return new dojo.lfx.Chain(_343); +}; +dojo.provide("dojo.graphics.color"); +dojo.require("dojo.lang.array"); +dojo.graphics.color.Color=function(r,g,b,a){ +if(dojo.lang.isArray(r)){ +this.r=r[0]; +this.g=r[1]; +this.b=r[2]; +this.a=r[3]||1; +}else{ +if(dojo.lang.isString(r)){ +var rgb=dojo.graphics.color.extractRGB(r); +this.r=rgb[0]; +this.g=rgb[1]; +this.b=rgb[2]; +this.a=g||1; +}else{ +if(r instanceof dojo.graphics.color.Color){ +this.r=r.r; +this.b=r.b; +this.g=r.g; +this.a=r.a; +}else{ +this.r=r; +this.g=g; +this.b=b; +this.a=a; +} +} +} +}; +dojo.graphics.color.Color.fromArray=function(arr){ +return new dojo.graphics.color.Color(arr[0],arr[1],arr[2],arr[3]); +}; +dojo.lang.extend(dojo.graphics.color.Color,{toRgb:function(_34a){ +if(_34a){ +return this.toRgba(); +}else{ +return [this.r,this.g,this.b]; +} +},toRgba:function(){ +return [this.r,this.g,this.b,this.a]; +},toHex:function(){ +return dojo.graphics.color.rgb2hex(this.toRgb()); +},toCss:function(){ +return "rgb("+this.toRgb().join()+")"; +},toString:function(){ +return this.toHex(); +},blend:function(_34b,_34c){ +return dojo.graphics.color.blend(this.toRgb(),new dojo.graphics.color.Color(_34b).toRgb(),_34c); +}}); +dojo.graphics.color.named={white:[255,255,255],black:[0,0,0],red:[255,0,0],green:[0,255,0],blue:[0,0,255],navy:[0,0,128],gray:[128,128,128],silver:[192,192,192]}; +dojo.graphics.color.blend=function(a,b,_34f){ +if(typeof a=="string"){ +return dojo.graphics.color.blendHex(a,b,_34f); +} +if(!_34f){ +_34f=0; +}else{ +if(_34f>1){ +_34f=1; +}else{ +if(_34f<-1){ +_34f=-1; +} +} +} +var c=new Array(3); +for(var i=0;i<3;i++){ +var half=Math.abs(a[i]-b[i])/2; +c[i]=Math.floor(Math.min(a[i],b[i])+half+(half*_34f)); +} +return c; +}; +dojo.graphics.color.blendHex=function(a,b,_355){ +return dojo.graphics.color.rgb2hex(dojo.graphics.color.blend(dojo.graphics.color.hex2rgb(a),dojo.graphics.color.hex2rgb(b),_355)); +}; +dojo.graphics.color.extractRGB=function(_356){ +var hex="0123456789abcdef"; +_356=_356.toLowerCase(); +if(_356.indexOf("rgb")==0){ +var _358=_356.match(/rgba*\((\d+), *(\d+), *(\d+)/i); +var ret=_358.splice(1,3); +return ret; +}else{ +var _35a=dojo.graphics.color.hex2rgb(_356); +if(_35a){ +return _35a; +}else{ +return dojo.graphics.color.named[_356]||[255,255,255]; +} +} +}; +dojo.graphics.color.hex2rgb=function(hex){ +var _35c="0123456789ABCDEF"; +var rgb=new Array(3); +if(hex.indexOf("#")==0){ +hex=hex.substring(1); +} +hex=hex.toUpperCase(); +if(hex.replace(new RegExp("["+_35c+"]","g"),"")!=""){ +return null; +} +if(hex.length==3){ +rgb[0]=hex.charAt(0)+hex.charAt(0); +rgb[1]=hex.charAt(1)+hex.charAt(1); +rgb[2]=hex.charAt(2)+hex.charAt(2); +}else{ +rgb[0]=hex.substring(0,2); +rgb[1]=hex.substring(2,4); +rgb[2]=hex.substring(4); +} +for(var i=0;i0&&!(j==1&&segs[0]=="")&&segs[j]==".."&&segs[j-1]!=".."){ +if(j==segs.length-1){ +segs.splice(j,1); +segs[j-1]=""; +}else{ +segs.splice(j-1,2); +j-=2; +} +} +} +} +_36a.path=segs.join("/"); +} +} +} +} +uri=""; +if(_36a.scheme!=null){ +uri+=_36a.scheme+":"; +} +if(_36a.authority!=null){ +uri+="//"+_36a.authority; +} +uri+=_36a.path; +if(_36a.query!=null){ +uri+="?"+_36a.query; +} +if(_36a.fragment!=null){ +uri+="#"+_36a.fragment; +} +} +this.uri=uri.toString(); +var _36f="^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$"; +var r=this.uri.match(new RegExp(_36f)); +this.scheme=r[2]||(r[1]?"":null); +this.authority=r[4]||(r[3]?"":null); +this.path=r[5]; +this.query=r[7]||(r[6]?"":null); +this.fragment=r[9]||(r[8]?"":null); +if(this.authority!=null){ +_36f="^((([^:]+:)?([^@]+))@)?([^:]*)(:([0-9]+))?$"; +r=this.authority.match(new RegExp(_36f)); +this.user=r[3]||null; +this.password=r[4]||null; +this.host=r[5]; +this.port=r[7]||null; +} +this.toString=function(){ +return this.uri; +}; +}; +}; +dojo.provide("dojo.style"); +dojo.require("dojo.graphics.color"); +dojo.require("dojo.uri.Uri"); +dojo.require("dojo.lang.common"); +(function(){ +var h=dojo.render.html; +var ds=dojo.style; +var db=document["body"]||document["documentElement"]; +ds.boxSizing={MARGIN_BOX:"margin-box",BORDER_BOX:"border-box",PADDING_BOX:"padding-box",CONTENT_BOX:"content-box"}; +var bs=ds.boxSizing; +ds.getBoxSizing=function(node){ +if((h.ie)||(h.opera)){ +var cm=document["compatMode"]; +if((cm=="BackCompat")||(cm=="QuirksMode")){ +return bs.BORDER_BOX; +}else{ +return bs.CONTENT_BOX; +} +}else{ +if(arguments.length==0){ +node=document.documentElement; +} +var _377=ds.getStyle(node,"-moz-box-sizing"); +if(!_377){ +_377=ds.getStyle(node,"box-sizing"); +} +return (_377?_377:bs.CONTENT_BOX); +} +}; +ds.isBorderBox=function(node){ +return (ds.getBoxSizing(node)==bs.BORDER_BOX); +}; +ds.getUnitValue=function(node,_37a,_37b){ +var s=ds.getComputedStyle(node,_37a); +if((!s)||((s=="auto")&&(_37b))){ +return {value:0,units:"px"}; +} +if(dojo.lang.isUndefined(s)){ +return ds.getUnitValue.bad; +} +var _37d=s.match(/(\-?[\d.]+)([a-z%]*)/i); +if(!_37d){ +return ds.getUnitValue.bad; +} +return {value:Number(_37d[1]),units:_37d[2].toLowerCase()}; +}; +ds.getUnitValue.bad={value:NaN,units:""}; +ds.getPixelValue=function(node,_37f,_380){ +var _381=ds.getUnitValue(node,_37f,_380); +if(isNaN(_381.value)){ +return 0; +} +if((_381.value)&&(_381.units!="px")){ +return NaN; +} +return _381.value; +}; +ds.getNumericStyle=function(){ +dojo.deprecated("dojo.(style|html).getNumericStyle","in favor of dojo.(style|html).getPixelValue","0.4"); +return ds.getPixelValue.apply(this,arguments); +}; +ds.setPositivePixelValue=function(node,_383,_384){ +if(isNaN(_384)){ +return false; +} +node.style[_383]=Math.max(0,_384)+"px"; +return true; +}; +ds._sumPixelValues=function(node,_386,_387){ +var _388=0; +for(var x=0;x<_386.length;x++){ +_388+=ds.getPixelValue(node,_386[x],_387); +} +return _388; +}; +ds.isPositionAbsolute=function(node){ +return (ds.getComputedStyle(node,"position")=="absolute"); +}; +ds.getBorderExtent=function(node,side){ +return (ds.getStyle(node,"border-"+side+"-style")=="none"?0:ds.getPixelValue(node,"border-"+side+"-width")); +}; +ds.getMarginWidth=function(node){ +return ds._sumPixelValues(node,["margin-left","margin-right"],ds.isPositionAbsolute(node)); +}; +ds.getBorderWidth=function(node){ +return ds.getBorderExtent(node,"left")+ds.getBorderExtent(node,"right"); +}; +ds.getPaddingWidth=function(node){ +return ds._sumPixelValues(node,["padding-left","padding-right"],true); +}; +ds.getPadBorderWidth=function(node){ +return ds.getPaddingWidth(node)+ds.getBorderWidth(node); +}; +ds.getContentBoxWidth=function(node){ +node=dojo.byId(node); +return node.offsetWidth-ds.getPadBorderWidth(node); +}; +ds.getBorderBoxWidth=function(node){ +node=dojo.byId(node); +return node.offsetWidth; +}; +ds.getMarginBoxWidth=function(node){ +return ds.getInnerWidth(node)+ds.getMarginWidth(node); +}; +ds.setContentBoxWidth=function(node,_395){ +node=dojo.byId(node); +if(ds.isBorderBox(node)){ +_395+=ds.getPadBorderWidth(node); +} +return ds.setPositivePixelValue(node,"width",_395); +}; +ds.setMarginBoxWidth=function(node,_397){ +node=dojo.byId(node); +if(!ds.isBorderBox(node)){ +_397-=ds.getPadBorderWidth(node); +} +_397-=ds.getMarginWidth(node); +return ds.setPositivePixelValue(node,"width",_397); +}; +ds.getContentWidth=ds.getContentBoxWidth; +ds.getInnerWidth=ds.getBorderBoxWidth; +ds.getOuterWidth=ds.getMarginBoxWidth; +ds.setContentWidth=ds.setContentBoxWidth; +ds.setOuterWidth=ds.setMarginBoxWidth; +ds.getMarginHeight=function(node){ +return ds._sumPixelValues(node,["margin-top","margin-bottom"],ds.isPositionAbsolute(node)); +}; +ds.getBorderHeight=function(node){ +return ds.getBorderExtent(node,"top")+ds.getBorderExtent(node,"bottom"); +}; +ds.getPaddingHeight=function(node){ +return ds._sumPixelValues(node,["padding-top","padding-bottom"],true); +}; +ds.getPadBorderHeight=function(node){ +return ds.getPaddingHeight(node)+ds.getBorderHeight(node); +}; +ds.getContentBoxHeight=function(node){ +node=dojo.byId(node); +return node.offsetHeight-ds.getPadBorderHeight(node); +}; +ds.getBorderBoxHeight=function(node){ +node=dojo.byId(node); +return node.offsetHeight; +}; +ds.getMarginBoxHeight=function(node){ +return ds.getInnerHeight(node)+ds.getMarginHeight(node); +}; +ds.setContentBoxHeight=function(node,_3a0){ +node=dojo.byId(node); +if(ds.isBorderBox(node)){ +_3a0+=ds.getPadBorderHeight(node); +} +return ds.setPositivePixelValue(node,"height",_3a0); +}; +ds.setMarginBoxHeight=function(node,_3a2){ +node=dojo.byId(node); +if(!ds.isBorderBox(node)){ +_3a2-=ds.getPadBorderHeight(node); +} +_3a2-=ds.getMarginHeight(node); +return ds.setPositivePixelValue(node,"height",_3a2); +}; +ds.getContentHeight=ds.getContentBoxHeight; +ds.getInnerHeight=ds.getBorderBoxHeight; +ds.getOuterHeight=ds.getMarginBoxHeight; +ds.setContentHeight=ds.setContentBoxHeight; +ds.setOuterHeight=ds.setMarginBoxHeight; +ds.getAbsolutePosition=ds.abs=function(node,_3a4){ +node=dojo.byId(node); +var ret=[]; +ret.x=ret.y=0; +var st=dojo.html.getScrollTop(); +var sl=dojo.html.getScrollLeft(); +if(h.ie){ +with(node.getBoundingClientRect()){ +ret.x=left-2; +ret.y=top-2; +} +}else{ +if(document.getBoxObjectFor){ +var bo=document.getBoxObjectFor(node); +ret.x=bo.x-ds.sumAncestorProperties(node,"scrollLeft"); +ret.y=bo.y-ds.sumAncestorProperties(node,"scrollTop"); +}else{ +if(node["offsetParent"]){ +var _3a9; +if((h.safari)&&(node.style.getPropertyValue("position")=="absolute")&&(node.parentNode==db)){ +_3a9=db; +}else{ +_3a9=db.parentNode; +} +if(node.parentNode!=db){ +var nd=node; +if(window.opera){ +nd=db; +} +ret.x-=ds.sumAncestorProperties(nd,"scrollLeft"); +ret.y-=ds.sumAncestorProperties(nd,"scrollTop"); +} +do{ +var n=node["offsetLeft"]; +ret.x+=isNaN(n)?0:n; +var m=node["offsetTop"]; +ret.y+=isNaN(m)?0:m; +node=node.offsetParent; +}while((node!=_3a9)&&(node!=null)); +}else{ +if(node["x"]&&node["y"]){ +ret.x+=isNaN(node.x)?0:node.x; +ret.y+=isNaN(node.y)?0:node.y; +} +} +} +} +if(_3a4){ +ret.y+=st; +ret.x+=sl; +} +ret[0]=ret.x; +ret[1]=ret.y; +return ret; +}; +ds.sumAncestorProperties=function(node,prop){ +node=dojo.byId(node); +if(!node){ +return 0; +} +var _3af=0; +while(node){ +var val=node[prop]; +if(val){ +_3af+=val-0; +if(node==document.body){ +break; +} +} +node=node.parentNode; +} +return _3af; +}; +ds.getTotalOffset=function(node,type,_3b3){ +return ds.abs(node,_3b3)[(type=="top")?"y":"x"]; +}; +ds.getAbsoluteX=ds.totalOffsetLeft=function(node,_3b5){ +return ds.getTotalOffset(node,"left",_3b5); +}; +ds.getAbsoluteY=ds.totalOffsetTop=function(node,_3b7){ +return ds.getTotalOffset(node,"top",_3b7); +}; +ds.styleSheet=null; +ds.insertCssRule=function(_3b8,_3b9,_3ba){ +if(!ds.styleSheet){ +if(document.createStyleSheet){ +ds.styleSheet=document.createStyleSheet(); +}else{ +if(document.styleSheets[0]){ +ds.styleSheet=document.styleSheets[0]; +}else{ +return null; +} +} +} +if(arguments.length<3){ +if(ds.styleSheet.cssRules){ +_3ba=ds.styleSheet.cssRules.length; +}else{ +if(ds.styleSheet.rules){ +_3ba=ds.styleSheet.rules.length; +}else{ +return null; +} +} +} +if(ds.styleSheet.insertRule){ +var rule=_3b8+" { "+_3b9+" }"; +return ds.styleSheet.insertRule(rule,_3ba); +}else{ +if(ds.styleSheet.addRule){ +return ds.styleSheet.addRule(_3b8,_3b9,_3ba); +}else{ +return null; +} +} +}; +ds.removeCssRule=function(_3bc){ +if(!ds.styleSheet){ +dojo.debug("no stylesheet defined for removing rules"); +return false; +} +if(h.ie){ +if(!_3bc){ +_3bc=ds.styleSheet.rules.length; +ds.styleSheet.removeRule(_3bc); +} +}else{ +if(document.styleSheets[0]){ +if(!_3bc){ +_3bc=ds.styleSheet.cssRules.length; +} +ds.styleSheet.deleteRule(_3bc); +} +} +return true; +}; +ds.insertCssFile=function(URI,doc,_3bf){ +if(!URI){ +return; +} +if(!doc){ +doc=document; +} +var _3c0=dojo.hostenv.getText(URI); +_3c0=ds.fixPathsInCssText(_3c0,URI); +if(_3bf){ +var _3c1=doc.getElementsByTagName("style"); +var _3c2=""; +for(var i=0;i<_3c1.length;i++){ +_3c2=(_3c1[i].styleSheet&&_3c1[i].styleSheet.cssText)?_3c1[i].styleSheet.cssText:_3c1[i].innerHTML; +if(_3c0==_3c2){ +return; +} +} +} +var _3c4=ds.insertCssText(_3c0); +if(_3c4&&djConfig.isDebug){ +_3c4.setAttribute("dbgHref",URI); +} +return _3c4; +}; +ds.insertCssText=function(_3c5,doc,URI){ +if(!_3c5){ +return; +} +if(!doc){ +doc=document; +} +if(URI){ +_3c5=ds.fixPathsInCssText(_3c5,URI); +} +var _3c8=doc.createElement("style"); +_3c8.setAttribute("type","text/css"); +var head=doc.getElementsByTagName("head")[0]; +if(!head){ +dojo.debug("No head tag in document, aborting styles"); +return; +}else{ +head.appendChild(_3c8); +} +if(_3c8.styleSheet){ +_3c8.styleSheet.cssText=_3c5; +}else{ +var _3ca=doc.createTextNode(_3c5); +_3c8.appendChild(_3ca); +} +return _3c8; +}; +ds.fixPathsInCssText=function(_3cb,URI){ +if(!_3cb||!URI){ +return; +} +var pos=0; +var str=""; +var url=""; +while(pos!=-1){ +pos=0; +url=""; +pos=_3cb.indexOf("url(",pos); +if(pos<0){ +break; +} +str+=_3cb.slice(0,pos+4); +_3cb=_3cb.substring(pos+4,_3cb.length); +url+=_3cb.match(/^[\t\s\w()\/.\\'"-:#=&?]*\)/)[0]; +_3cb=_3cb.substring(url.length-1,_3cb.length); +url=url.replace(/^[\s\t]*(['"]?)([\w()\/.\\'"-:#=&?]*)\1[\s\t]*?\)/,"$2"); +if(url.search(/(file|https?|ftps?):\/\//)==-1){ +url=(new dojo.uri.Uri(URI,url).toString()); +} +str+=url; +} +return str+_3cb; +}; +ds.getBackgroundColor=function(node){ +node=dojo.byId(node); +var _3d1; +do{ +_3d1=ds.getStyle(node,"background-color"); +if(_3d1.toLowerCase()=="rgba(0, 0, 0, 0)"){ +_3d1="transparent"; +} +if(node==document.getElementsByTagName("body")[0]){ +node=null; +break; +} +node=node.parentNode; +}while(node&&dojo.lang.inArray(_3d1,["transparent",""])); +if(_3d1=="transparent"){ +_3d1=[255,255,255,0]; +}else{ +_3d1=dojo.graphics.color.extractRGB(_3d1); +} +return _3d1; +}; +ds.getComputedStyle=function(node,_3d3,_3d4){ +node=dojo.byId(node); +var _3d3=ds.toSelectorCase(_3d3); +var _3d5=ds.toCamelCase(_3d3); +if(!node||!node.style){ +return _3d4; +}else{ +if(document.defaultView){ +try{ +var cs=document.defaultView.getComputedStyle(node,""); +if(cs){ +return cs.getPropertyValue(_3d3); +} +} +catch(e){ +if(node.style.getPropertyValue){ +return node.style.getPropertyValue(_3d3); +}else{ +return _3d4; +} +} +}else{ +if(node.currentStyle){ +return node.currentStyle[_3d5]; +} +} +} +if(node.style.getPropertyValue){ +return node.style.getPropertyValue(_3d3); +}else{ +return _3d4; +} +}; +ds.getStyleProperty=function(node,_3d8){ +node=dojo.byId(node); +return (node&&node.style?node.style[ds.toCamelCase(_3d8)]:undefined); +}; +ds.getStyle=function(node,_3da){ +var _3db=ds.getStyleProperty(node,_3da); +return (_3db?_3db:ds.getComputedStyle(node,_3da)); +}; +ds.setStyle=function(node,_3dd,_3de){ +node=dojo.byId(node); +if(node&&node.style){ +var _3df=ds.toCamelCase(_3dd); +node.style[_3df]=_3de; +} +}; +ds.toCamelCase=function(_3e0){ +var arr=_3e0.split("-"),cc=arr[0]; +for(var i=1;i=1){ +if(h.ie){ +ds.clearOpacity(node); +return; +}else{ +_3e5=0.999999; +} +}else{ +if(_3e5<0){ +_3e5=0; +} +} +} +if(h.ie){ +if(node.nodeName.toLowerCase()=="tr"){ +var tds=node.getElementsByTagName("td"); +for(var x=0;x=0.999999?1:Number(opac); +}; +ds.clearOpacity=function clearOpacity(node){ +node=dojo.byId(node); +var ns=node.style; +if(h.ie){ +try{ +if(node.filters&&node.filters.alpha){ +ns.filter=""; +} +} +catch(e){ +} +}else{ +if(h.moz){ +ns.opacity=1; +ns.MozOpacity=1; +}else{ +if(h.safari){ +ns.opacity=1; +ns.KhtmlOpacity=1; +}else{ +ns.opacity=1; +} +} +} +}; +ds.setStyleAttributes=function(node,_3ee){ +var _3ef={"opacity":dojo.style.setOpacity,"content-height":dojo.style.setContentHeight,"content-width":dojo.style.setContentWidth,"outer-height":dojo.style.setOuterHeight,"outer-width":dojo.style.setOuterWidth}; +var _3f0=_3ee.replace(/(;)?\s*$/,"").split(";"); +for(var i=0;i<_3f0.length;i++){ +var _3f2=_3f0[i].split(":"); +var name=_3f2[0].replace(/\s*$/,"").replace(/^\s*/,"").toLowerCase(); +var _3f4=_3f2[1].replace(/\s*$/,"").replace(/^\s*/,""); +if(dojo.lang.has(_3ef,name)){ +_3ef[name](node,_3f4); +}else{ +node.style[dojo.style.toCamelCase(name)]=_3f4; +} +} +}; +ds._toggle=function(node,_3f6,_3f7){ +node=dojo.byId(node); +_3f7(node,!_3f6(node)); +return _3f6(node); +}; +ds.show=function(node){ +node=dojo.byId(node); +if(ds.getStyleProperty(node,"display")=="none"){ +ds.setStyle(node,"display",(node.dojoDisplayCache||"")); +node.dojoDisplayCache=undefined; +} +}; +ds.hide=function(node){ +node=dojo.byId(node); +if(typeof node["dojoDisplayCache"]=="undefined"){ +var d=ds.getStyleProperty(node,"display"); +if(d!="none"){ +node.dojoDisplayCache=d; +} +} +ds.setStyle(node,"display","none"); +}; +ds.setShowing=function(node,_3fc){ +ds[(_3fc?"show":"hide")](node); +}; +ds.isShowing=function(node){ +return (ds.getStyleProperty(node,"display")!="none"); +}; +ds.toggleShowing=function(node){ +return ds._toggle(node,ds.isShowing,ds.setShowing); +}; +ds.displayMap={tr:"",td:"",th:"",img:"inline",span:"inline",input:"inline",button:"inline"}; +ds.suggestDisplayByTagName=function(node){ +node=dojo.byId(node); +if(node&&node.tagName){ +var tag=node.tagName.toLowerCase(); +return (tag in ds.displayMap?ds.displayMap[tag]:"block"); +} +}; +ds.setDisplay=function(node,_402){ +ds.setStyle(node,"display",(dojo.lang.isString(_402)?_402:(_402?ds.suggestDisplayByTagName(node):"none"))); +}; +ds.isDisplayed=function(node){ +return (ds.getComputedStyle(node,"display")!="none"); +}; +ds.toggleDisplay=function(node){ +return ds._toggle(node,ds.isDisplayed,ds.setDisplay); +}; +ds.setVisibility=function(node,_406){ +ds.setStyle(node,"visibility",(dojo.lang.isString(_406)?_406:(_406?"visible":"hidden"))); +}; +ds.isVisible=function(node){ +return (ds.getComputedStyle(node,"visibility")!="hidden"); +}; +ds.toggleVisibility=function(node){ +return ds._toggle(node,ds.isVisible,ds.setVisibility); +}; +ds.toCoordinateArray=function(_409,_40a){ +if(dojo.lang.isArray(_409)){ +while(_409.length<4){ +_409.push(0); +} +while(_409.length>4){ +_409.pop(); +} +var ret=_409; +}else{ +var node=dojo.byId(_409); +var pos=ds.getAbsolutePosition(node,_40a); +var ret=[pos.x,pos.y,ds.getBorderBoxWidth(node),ds.getBorderBoxHeight(node)]; +} +ret.x=ret[0]; +ret.y=ret[1]; +ret.w=ret[2]; +ret.h=ret[3]; +return ret; +}; +})(); +dojo.provide("dojo.html"); +dojo.require("dojo.lang.func"); +dojo.require("dojo.dom"); +dojo.require("dojo.style"); +dojo.require("dojo.string"); +dojo.lang.mixin(dojo.html,dojo.dom); +dojo.lang.mixin(dojo.html,dojo.style); +dojo.html.clearSelection=function(){ +try{ +if(window["getSelection"]){ +if(dojo.render.html.safari){ +window.getSelection().collapse(); +}else{ +window.getSelection().removeAllRanges(); +} +}else{ +if(document.selection){ +if(document.selection.empty){ +document.selection.empty(); +}else{ +if(document.selection.clear){ +document.selection.clear(); +} +} +} +} +return true; +} +catch(e){ +dojo.debug(e); +return false; +} +}; +dojo.html.disableSelection=function(_40e){ +_40e=dojo.byId(_40e)||document.body; +var h=dojo.render.html; +if(h.mozilla){ +_40e.style.MozUserSelect="none"; +}else{ +if(h.safari){ +_40e.style.KhtmlUserSelect="none"; +}else{ +if(h.ie){ +_40e.unselectable="on"; +}else{ +return false; +} +} +} +return true; +}; +dojo.html.enableSelection=function(_410){ +_410=dojo.byId(_410)||document.body; +var h=dojo.render.html; +if(h.mozilla){ +_410.style.MozUserSelect=""; +}else{ +if(h.safari){ +_410.style.KhtmlUserSelect=""; +}else{ +if(h.ie){ +_410.unselectable="off"; +}else{ +return false; +} +} +} +return true; +}; +dojo.html.selectElement=function(_412){ +_412=dojo.byId(_412); +if(document.selection&&document.body.createTextRange){ +var _413=document.body.createTextRange(); +_413.moveToElementText(_412); +_413.select(); +}else{ +if(window["getSelection"]){ +var _414=window.getSelection(); +if(_414["selectAllChildren"]){ +_414.selectAllChildren(_412); +} +} +} +}; +dojo.html.selectInputText=function(_415){ +_415=dojo.byId(_415); +if(document.selection&&document.body.createTextRange){ +var _416=_415.createTextRange(); +_416.moveStart("character",0); +_416.moveEnd("character",_415.value.length); +_416.select(); +}else{ +if(window["getSelection"]){ +var _417=window.getSelection(); +_415.setSelectionRange(0,_415.value.length); +} +} +_415.focus(); +}; +dojo.html.isSelectionCollapsed=function(){ +if(document["selection"]){ +return document.selection.createRange().text==""; +}else{ +if(window["getSelection"]){ +var _418=window.getSelection(); +if(dojo.lang.isString(_418)){ +return _418==""; +}else{ +return _418.isCollapsed; +} +} +} +}; +dojo.html.getEventTarget=function(evt){ +if(!evt){ +evt=window.event||{}; +} +var t=(evt.srcElement?evt.srcElement:(evt.target?evt.target:null)); +while((t)&&(t.nodeType!=1)){ +t=t.parentNode; +} +return t; +}; +dojo.html.getDocumentWidth=function(){ +dojo.deprecated("dojo.html.getDocument*","replaced by dojo.html.getViewport*","0.4"); +return dojo.html.getViewportWidth(); +}; +dojo.html.getDocumentHeight=function(){ +dojo.deprecated("dojo.html.getDocument*","replaced by dojo.html.getViewport*","0.4"); +return dojo.html.getViewportHeight(); +}; +dojo.html.getDocumentSize=function(){ +dojo.deprecated("dojo.html.getDocument*","replaced of dojo.html.getViewport*","0.4"); +return dojo.html.getViewportSize(); +}; +dojo.html.getViewportWidth=function(){ +var w=0; +if(window.innerWidth){ +w=window.innerWidth; +} +if(dojo.exists(document,"documentElement.clientWidth")){ +var w2=document.documentElement.clientWidth; +if(!w||w2&&w2=left&&_456.x<=_45a&&_456.y>=top&&_456.y<=_458); +}; +dojo.html.setActiveStyleSheet=function(_45b){ +var i=0,a,els=document.getElementsByTagName("link"); +while(a=els[i++]){ +if(a.getAttribute("rel").indexOf("style")!=-1&&a.getAttribute("title")){ +a.disabled=true; +if(a.getAttribute("title")==_45b){ +a.disabled=false; +} +} +} +}; +dojo.html.getActiveStyleSheet=function(){ +var i=0,a,els=document.getElementsByTagName("link"); +while(a=els[i++]){ +if(a.getAttribute("rel").indexOf("style")!=-1&&a.getAttribute("title")&&!a.disabled){ +return a.getAttribute("title"); +} +} +return null; +}; +dojo.html.getPreferredStyleSheet=function(){ +var i=0,a,els=document.getElementsByTagName("link"); +while(a=els[i++]){ +if(a.getAttribute("rel").indexOf("style")!=-1&&a.getAttribute("rel").indexOf("alt")==-1&&a.getAttribute("title")){ +return a.getAttribute("title"); +} +} +return null; +}; +dojo.html.body=function(){ +return document.body||document.getElementsByTagName("body")[0]; +}; +dojo.html.isTag=function(node){ +node=dojo.byId(node); +if(node&&node.tagName){ +var arr=dojo.lang.map(dojo.lang.toArray(arguments,1),function(a){ +return String(a).toLowerCase(); +}); +return arr[dojo.lang.find(node.tagName.toLowerCase(),arr)]||""; +} +return ""; +}; +dojo.html.copyStyle=function(_462,_463){ +if(dojo.lang.isUndefined(_463.style.cssText)){ +_462.setAttribute("style",_463.getAttribute("style")); +}else{ +_462.style.cssText=_463.style.cssText; +} +dojo.html.addClass(_462,dojo.html.getClass(_463)); +}; +dojo.html._callExtrasDeprecated=function(_464,args){ +var _466="dojo.html.extras"; +dojo.deprecated("dojo.html."+_464,"moved to "+_466,"0.4"); +dojo["require"](_466); +return dojo.html[_464].apply(dojo.html,args); +}; +dojo.html.createNodesFromText=function(){ +return dojo.html._callExtrasDeprecated("createNodesFromText",arguments); +}; +dojo.html.gravity=function(){ +return dojo.html._callExtrasDeprecated("gravity",arguments); +}; +dojo.html.placeOnScreen=function(){ +return dojo.html._callExtrasDeprecated("placeOnScreen",arguments); +}; +dojo.html.placeOnScreenPoint=function(){ +return dojo.html._callExtrasDeprecated("placeOnScreenPoint",arguments); +}; +dojo.html.renderedTextContent=function(){ +return dojo.html._callExtrasDeprecated("renderedTextContent",arguments); +}; +dojo.html.BackgroundIframe=function(){ +return dojo.html._callExtrasDeprecated("BackgroundIframe",arguments); +}; +dojo.provide("dojo.lfx.html"); +dojo.require("dojo.lfx.Animation"); +dojo.require("dojo.html"); +dojo.lfx.html._byId=function(_467){ +if(!_467){ +return []; +} +if(dojo.lang.isArray(_467)){ +if(!_467.alreadyChecked){ +var n=[]; +dojo.lang.forEach(_467,function(node){ +n.push(dojo.byId(node)); +}); +n.alreadyChecked=true; +return n; +}else{ +return _467; +} +}else{ +var n=[]; +n.push(dojo.byId(_467)); +n.alreadyChecked=true; +return n; +} +}; +dojo.lfx.html.propertyAnimation=function(_46a,_46b,_46c,_46d){ +_46a=dojo.lfx.html._byId(_46a); +if(_46a.length==1){ +dojo.lang.forEach(_46b,function(prop){ +if(typeof prop["start"]=="undefined"){ +if(prop.property!="opacity"){ +prop.start=parseInt(dojo.style.getComputedStyle(_46a[0],prop.property)); +}else{ +prop.start=dojo.style.getOpacity(_46a[0]); +} +} +}); +} +var _46f=function(_470){ +var _471=new Array(_470.length); +for(var i=0;i<_470.length;i++){ +_471[i]=Math.round(_470[i]); +} +return _471; +}; +var _473=function(n,_475){ +n=dojo.byId(n); +if(!n||!n.style){ +return; +} +for(var s in _475){ +if(s=="opacity"){ +dojo.style.setOpacity(n,_475[s]); +}else{ +n.style[s]=_475[s]; +} +} +}; +var _477=function(_478){ +this._properties=_478; +this.diffs=new Array(_478.length); +dojo.lang.forEach(_478,function(prop,i){ +if(dojo.lang.isArray(prop.start)){ +this.diffs[i]=null; +}else{ +if(prop.start instanceof dojo.graphics.color.Color){ +prop.startRgb=prop.start.toRgb(); +prop.endRgb=prop.end.toRgb(); +}else{ +this.diffs[i]=prop.end-prop.start; +} +} +},this); +this.getValue=function(n){ +var ret={}; +dojo.lang.forEach(this._properties,function(prop,i){ +var _47f=null; +if(dojo.lang.isArray(prop.start)){ +}else{ +if(prop.start instanceof dojo.graphics.color.Color){ +_47f=(prop.units||"rgb")+"("; +for(var j=0;j1){ +return dojo.lfx.combine(_4a2); +}else{ +return _4a2[0]; +} +}; +dojo.lfx.html.wipeOut=function(_4a7,_4a8,_4a9,_4aa){ +_4a7=dojo.lfx.html._byId(_4a7); +var _4ab=[]; +dojo.lang.forEach(_4a7,function(node){ +var _4ad=dojo.style.getStyle(node,"overflow"); +if(_4ad=="visible"){ +node.style.overflow="hidden"; +} +dojo.style.show(node); +var anim=dojo.lfx.propertyAnimation(node,[{property:"height",start:dojo.style.getContentBoxHeight(node),end:0}],_4a8,_4a9); +var _4af=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4af(); +dojo.style.hide(node); +node.style.overflow=_4ad; +if(_4aa){ +_4aa(node,anim); +} +}; +_4ab.push(anim); +}); +if(_4a7.length>1){ +return dojo.lfx.combine(_4ab); +}else{ +return _4ab[0]; +} +}; +dojo.lfx.html.slideTo=function(_4b0,_4b1,_4b2,_4b3,_4b4){ +_4b0=dojo.lfx.html._byId(_4b0); +var _4b5=[]; +dojo.lang.forEach(_4b0,function(node){ +var top=null; +var left=null; +var init=(function(){ +var _4ba=node; +return function(){ +top=_4ba.offsetTop; +left=_4ba.offsetLeft; +if(!dojo.style.isPositionAbsolute(_4ba)){ +var ret=dojo.style.abs(_4ba,true); +dojo.style.setStyleAttributes(_4ba,"position:absolute;top:"+ret.y+"px;left:"+ret.x+"px;"); +top=ret.y; +left=ret.x; +} +}; +})(); +init(); +var anim=dojo.lfx.propertyAnimation(node,[{property:"top",start:top,end:_4b1[0]},{property:"left",start:left,end:_4b1[1]}],_4b2,_4b3); +var _4bd=(anim["beforeBegin"])?dojo.lang.hitch(anim,"beforeBegin"):function(){ +}; +anim.beforeBegin=function(){ +_4bd(); +init(); +}; +if(_4b4){ +var _4be=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4be(); +_4b4(_4b0,anim); +}; +} +_4b5.push(anim); +}); +if(_4b0.length>1){ +return dojo.lfx.combine(_4b5); +}else{ +return _4b5[0]; +} +}; +dojo.lfx.html.slideBy=function(_4bf,_4c0,_4c1,_4c2,_4c3){ +_4bf=dojo.lfx.html._byId(_4bf); +var _4c4=[]; +dojo.lang.forEach(_4bf,function(node){ +var top=null; +var left=null; +var init=(function(){ +var _4c9=node; +return function(){ +top=node.offsetTop; +left=node.offsetLeft; +if(!dojo.style.isPositionAbsolute(_4c9)){ +var ret=dojo.style.abs(_4c9); +dojo.style.setStyleAttributes(_4c9,"position:absolute;top:"+ret.y+"px;left:"+ret.x+"px;"); +top=ret.y; +left=ret.x; +} +}; +})(); +init(); +var anim=dojo.lfx.propertyAnimation(node,[{property:"top",start:top,end:top+_4c0[0]},{property:"left",start:left,end:left+_4c0[1]}],_4c1,_4c2); +var _4cc=(anim["beforeBegin"])?dojo.lang.hitch(anim,"beforeBegin"):function(){ +}; +anim.beforeBegin=function(){ +_4cc(); +init(); +}; +if(_4c3){ +var _4cd=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4cd(); +_4c3(_4bf,anim); +}; +} +_4c4.push(anim); +}); +if(_4bf.length>1){ +return dojo.lfx.combine(_4c4); +}else{ +return _4c4[0]; +} +}; +dojo.lfx.html.explode=function(_4ce,_4cf,_4d0,_4d1,_4d2){ +_4ce=dojo.byId(_4ce); +_4cf=dojo.byId(_4cf); +var _4d3=dojo.style.toCoordinateArray(_4ce,true); +var _4d4=document.createElement("div"); +dojo.html.copyStyle(_4d4,_4cf); +with(_4d4.style){ +position="absolute"; +display="none"; +} +document.body.appendChild(_4d4); +with(_4cf.style){ +visibility="hidden"; +display="block"; +} +var _4d5=dojo.style.toCoordinateArray(_4cf,true); +with(_4cf.style){ +display="none"; +visibility="visible"; +} +var anim=new dojo.lfx.propertyAnimation(_4d4,[{property:"height",start:_4d3[3],end:_4d5[3]},{property:"width",start:_4d3[2],end:_4d5[2]},{property:"top",start:_4d3[1],end:_4d5[1]},{property:"left",start:_4d3[0],end:_4d5[0]},{property:"opacity",start:0.3,end:1}],_4d0,_4d1); +anim.beforeBegin=function(){ +dojo.style.setDisplay(_4d4,"block"); +}; +anim.onEnd=function(){ +dojo.style.setDisplay(_4cf,"block"); +_4d4.parentNode.removeChild(_4d4); +}; +if(_4d2){ +var _4d7=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4d7(); +_4d2(_4cf,anim); +}; +} +return anim; +}; +dojo.lfx.html.implode=function(_4d8,end,_4da,_4db,_4dc){ +_4d8=dojo.byId(_4d8); +end=dojo.byId(end); +var _4dd=dojo.style.toCoordinateArray(_4d8,true); +var _4de=dojo.style.toCoordinateArray(end,true); +var _4df=document.createElement("div"); +dojo.html.copyStyle(_4df,_4d8); +dojo.style.setOpacity(_4df,0.3); +with(_4df.style){ +position="absolute"; +display="none"; +} +document.body.appendChild(_4df); +var anim=new dojo.lfx.propertyAnimation(_4df,[{property:"height",start:_4dd[3],end:_4de[3]},{property:"width",start:_4dd[2],end:_4de[2]},{property:"top",start:_4dd[1],end:_4de[1]},{property:"left",start:_4dd[0],end:_4de[0]},{property:"opacity",start:1,end:0.3}],_4da,_4db); +anim.beforeBegin=function(){ +dojo.style.hide(_4d8); +dojo.style.show(_4df); +}; +anim.onEnd=function(){ +_4df.parentNode.removeChild(_4df); +}; +if(_4dc){ +var _4e1=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4e1(); +_4dc(_4d8,anim); +}; +} +return anim; +}; +dojo.lfx.html.highlight=function(_4e2,_4e3,_4e4,_4e5,_4e6){ +_4e2=dojo.lfx.html._byId(_4e2); +var _4e7=[]; +dojo.lang.forEach(_4e2,function(node){ +var _4e9=dojo.style.getBackgroundColor(node); +var bg=dojo.style.getStyle(node,"background-color").toLowerCase(); +var _4eb=dojo.style.getStyle(node,"background-image"); +var _4ec=(bg=="transparent"||bg=="rgba(0, 0, 0, 0)"); +while(_4e9.length>3){ +_4e9.pop(); +} +var rgb=new dojo.graphics.color.Color(_4e3); +var _4ee=new dojo.graphics.color.Color(_4e9); +var anim=dojo.lfx.propertyAnimation(node,[{property:"background-color",start:rgb,end:_4ee}],_4e4,_4e5); +var _4f0=(anim["beforeBegin"])?dojo.lang.hitch(anim,"beforeBegin"):function(){ +}; +anim.beforeBegin=function(){ +_4f0(); +if(_4eb){ +node.style.backgroundImage="none"; +} +node.style.backgroundColor="rgb("+rgb.toRgb().join(",")+")"; +}; +var _4f1=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4f1(); +if(_4eb){ +node.style.backgroundImage=_4eb; +} +if(_4ec){ +node.style.backgroundColor="transparent"; +} +if(_4e6){ +_4e6(node,anim); +} +}; +_4e7.push(anim); +}); +if(_4e2.length>1){ +return dojo.lfx.combine(_4e7); +}else{ +return _4e7[0]; +} +}; +dojo.lfx.html.unhighlight=function(_4f2,_4f3,_4f4,_4f5,_4f6){ +_4f2=dojo.lfx.html._byId(_4f2); +var _4f7=[]; +dojo.lang.forEach(_4f2,function(node){ +var _4f9=new dojo.graphics.color.Color(dojo.style.getBackgroundColor(node)); +var rgb=new dojo.graphics.color.Color(_4f3); +var _4fb=dojo.style.getStyle(node,"background-image"); +var anim=dojo.lfx.propertyAnimation(node,[{property:"background-color",start:_4f9,end:rgb}],_4f4,_4f5); +var _4fd=(anim["beforeBegin"])?dojo.lang.hitch(anim,"beforeBegin"):function(){ +}; +anim.beforeBegin=function(){ +_4fd(); +if(_4fb){ +node.style.backgroundImage="none"; +} +node.style.backgroundColor="rgb("+_4f9.toRgb().join(",")+")"; +}; +var _4fe=(anim["onEnd"])?dojo.lang.hitch(anim,"onEnd"):function(){ +}; +anim.onEnd=function(){ +_4fe(); +if(_4f6){ +_4f6(node,anim); +} +}; +_4f7.push(anim); +}); +if(_4f2.length>1){ +return dojo.lfx.combine(_4f7); +}else{ +return _4f7[0]; +} +}; +dojo.lang.mixin(dojo.lfx,dojo.lfx.html); +dojo.kwCompoundRequire({browser:["dojo.lfx.html"],dashboard:["dojo.lfx.html"]}); +dojo.provide("dojo.lfx.*"); + diff --git a/source/web/scripts/validation.js b/source/web/scripts/validation.js index c7ba9fe046..be31a382e1 100644 --- a/source/web/scripts/validation.js +++ b/source/web/scripts/validation.js @@ -52,6 +52,24 @@ function validateNumberRange(control, min, max, message, showMessage) return result; } +/** + * Ensures the value of the 'control' is a number. + * + * @return true if the value is a number + */ +function validateIsNumber(control, message, showMessage) +{ + var result = true; + + if (isNaN(control.value)) + { + informUser(control, message, showMessage); + result = false; + } + + return result; +} + /** * Ensures the value of the 'control' has a string length more than 'min' and less than 'max'. *