Repository: Bettermeans/bettermeans Branch: master Commit: 3cc796930874 Files: 1916 Total size: 7.9 MB Directory structure: gitextract_i_av7aco/ ├── .gitignore ├── .gitmodules ├── .rspec ├── .rvmrc ├── Gemfile ├── Guardfile ├── README.md ├── Rakefile ├── app/ │ ├── controllers/ │ │ ├── account_controller.rb │ │ ├── activity_stream_preferences_controller.rb │ │ ├── activity_stream_preferences_module.rb │ │ ├── activity_streams_controller.rb │ │ ├── activity_streams_module.rb │ │ ├── admin_controller.rb │ │ ├── application_controller.rb │ │ ├── attachments_controller.rb │ │ ├── auth_sources_controller.rb │ │ ├── boards_controller.rb │ │ ├── comments_controller.rb │ │ ├── credit_distributions_controller.rb │ │ ├── credit_transfers_controller.rb │ │ ├── credits_controller.rb │ │ ├── documents_controller.rb │ │ ├── email_updates_controller.rb │ │ ├── enterprises_controller.rb │ │ ├── enumerations_controller.rb │ │ ├── help_controller.rb │ │ ├── help_sections_controller.rb │ │ ├── home_controller.rb │ │ ├── hourly_types_controller.rb │ │ ├── invitations_controller.rb │ │ ├── issue_invitations_controller.rb │ │ ├── issue_relations_controller.rb │ │ ├── issue_statuses_controller.rb │ │ ├── issue_votes_controller.rb │ │ ├── issues_controller.rb │ │ ├── journals_controller.rb │ │ ├── mail_handler_controller.rb │ │ ├── mails_controller.rb │ │ ├── members_controller.rb │ │ ├── messages_controller.rb │ │ ├── motion_votes_controller.rb │ │ ├── motions_controller.rb │ │ ├── my_controller.rb │ │ ├── news_controller.rb │ │ ├── notifications_controller.rb │ │ ├── projects_controller.rb │ │ ├── queries_controller.rb │ │ ├── quotes_controller.rb │ │ ├── recurly_notifications_controller.rb │ │ ├── reports_controller.rb │ │ ├── reputations_controller.rb │ │ ├── retro_ratings_controller.rb │ │ ├── retros_controller.rb │ │ ├── roles_controller.rb │ │ ├── search_controller.rb │ │ ├── settings_controller.rb │ │ ├── shares_controller.rb │ │ ├── todos_controller.rb │ │ ├── trackers_controller.rb │ │ ├── users_controller.rb │ │ ├── votes_controller.rb │ │ ├── watchers_controller.rb │ │ ├── welcome_controller.rb │ │ ├── wiki_controller.rb │ │ ├── wikis_controller.rb │ │ └── workflows_controller.rb │ ├── helpers/ │ │ ├── admin_helper.rb │ │ ├── application_helper.rb │ │ ├── attachments_helper.rb │ │ ├── groups_helper.rb │ │ ├── issue_relations_helper.rb │ │ ├── issues_helper.rb │ │ ├── journals_helper.rb │ │ ├── messages_helper.rb │ │ ├── motions_helper.rb │ │ ├── my_helper.rb │ │ ├── news_helper.rb │ │ ├── projects_helper.rb │ │ ├── queries_helper.rb │ │ ├── reports_helper.rb │ │ ├── retros_helper.rb │ │ ├── search_helper.rb │ │ ├── settings_helper.rb │ │ ├── sort_helper.rb │ │ ├── users_helper.rb │ │ ├── watchers_helper.rb │ │ └── wiki_helper.rb │ ├── models/ │ │ ├── activity_stream.rb │ │ ├── activity_stream_preference.rb │ │ ├── activity_stream_total.rb │ │ ├── attachment.rb │ │ ├── auth_source.rb │ │ ├── auth_source_ldap.rb │ │ ├── board.rb │ │ ├── comment.rb │ │ ├── credit.rb │ │ ├── credit_distribution.rb │ │ ├── credit_transfer.rb │ │ ├── daily_digest.rb │ │ ├── document.rb │ │ ├── document_observer.rb │ │ ├── email_update.rb │ │ ├── enabled_module.rb │ │ ├── enterprise.rb │ │ ├── enumeration.rb │ │ ├── help_section.rb │ │ ├── hourly_type.rb │ │ ├── invitation.rb │ │ ├── issue.rb │ │ ├── issue_observer.rb │ │ ├── issue_relation.rb │ │ ├── issue_status.rb │ │ ├── issue_vote.rb │ │ ├── journal.rb │ │ ├── journal_detail.rb │ │ ├── journal_observer.rb │ │ ├── mail.rb │ │ ├── mail_handler.rb │ │ ├── mailer.rb │ │ ├── member.rb │ │ ├── member_role.rb │ │ ├── message.rb │ │ ├── message_observer.rb │ │ ├── motion.rb │ │ ├── motion_vote.rb │ │ ├── news.rb │ │ ├── news_observer.rb │ │ ├── notification.rb │ │ ├── open_id_authentication_association.rb │ │ ├── open_id_authentication_nonces.rb │ │ ├── personal_welcome.rb │ │ ├── plan.rb │ │ ├── plugin_schema_info.rb │ │ ├── project.rb │ │ ├── query.rb │ │ ├── quote.rb │ │ ├── reputation.rb │ │ ├── retro.rb │ │ ├── retro_rating.rb │ │ ├── role.rb │ │ ├── setting.rb │ │ ├── share.rb │ │ ├── todo.rb │ │ ├── token.rb │ │ ├── track.rb │ │ ├── tracker.rb │ │ ├── user.rb │ │ ├── user_preference.rb │ │ ├── watcher.rb │ │ ├── wiki.rb │ │ ├── wiki_content.rb │ │ ├── wiki_content_observer.rb │ │ ├── wiki_page.rb │ │ ├── wiki_redirect.rb │ │ └── workflow.rb │ └── views/ │ ├── account/ │ │ ├── login.html.erb │ │ ├── lost_password.html.erb │ │ ├── password_recovery.html.erb │ │ └── register.html.erb │ ├── activity_stream_preferences/ │ │ └── index.html.erb │ ├── activity_streams/ │ │ ├── _activity_comment.html.erb │ │ ├── _activity_stream.html.erb │ │ ├── _activity_stream_feed.atom.builder │ │ ├── _activity_stream_group.erb │ │ ├── _activity_stream_list.html.erb │ │ ├── _activity_stream_sub.html.erb │ │ ├── _indirect_object.html.erb │ │ ├── _table_header.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── admin/ │ │ ├── _menu.html.erb │ │ ├── _no_data.html.erb │ │ ├── index.html.erb │ │ ├── info.html.erb │ │ ├── plugins.html.erb │ │ ├── projects.html.erb │ │ └── user_stats.html.erb │ ├── attachments/ │ │ ├── _form.html.erb │ │ ├── _links.html.erb │ │ ├── _table.erb │ │ ├── diff.html.erb │ │ └── file.html.erb │ ├── auth_sources/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── list.html.erb │ │ └── new.html.erb │ ├── boards/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── common/ │ │ ├── 403.html.erb │ │ ├── 404.html.erb │ │ ├── _calendar.html.erb │ │ ├── _diff.html.erb │ │ ├── _file.html.erb │ │ ├── _main_menu_home.html.erb │ │ ├── _preview.html.erb │ │ ├── _tabs.html.erb │ │ └── feed.atom.rxml │ ├── credit_distributions/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── credit_transfers/ │ │ ├── _credit_transfer_history.html.erb │ │ ├── _eligible_recipients.erb │ │ ├── _new_credit_transfer.html.erb │ │ └── index.html.erb │ ├── credits/ │ │ ├── _credit_breakdown.html.erb │ │ ├── _credit_history.html.erb │ │ ├── _credit_queue.html.erb │ │ ├── _my_credits.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── documents/ │ │ ├── _document.html.erb │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── email_updates/ │ │ └── new.html.erb │ ├── enterprises/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── enumerations/ │ │ ├── _form.html.erb │ │ ├── destroy.html.erb │ │ ├── edit.html.erb │ │ ├── list.html.erb │ │ └── new.html.erb │ ├── help/ │ │ └── show.html.erb │ ├── help_sections/ │ │ ├── _show.html.erb │ │ ├── _show_popup.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── home/ │ │ ├── _footer.html.erb │ │ ├── _header.html.erb │ │ ├── about.html.erb │ │ ├── apps.html.erb │ │ ├── contact.html.erb │ │ ├── elements.html.erb │ │ ├── features_original.html.erb │ │ ├── how.html.erb │ │ ├── hq.html.erb │ │ ├── index.html.erb │ │ ├── library.html.erb │ │ ├── old_index.html.erb │ │ ├── old_index2.html.erb │ │ ├── old_inviteonline.html.erb │ │ ├── open_enterprise_governance_model.html.erb │ │ ├── pricing.html.erb │ │ ├── privacy.html.erb │ │ ├── services.html.erb │ │ ├── signup.html.erb │ │ ├── tour.html.erb │ │ ├── user_agreement.html.erb │ │ ├── webdesign.html.erb │ │ ├── what.html.erb │ │ └── why.html.erb │ ├── hourly_types/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── invitations/ │ │ ├── _generic_invitation.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── issue_relations/ │ │ └── _form.html.erb │ ├── issue_statuses/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── list.html.erb │ │ └── new.html.erb │ ├── issue_votes/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── issues/ │ │ ├── _action_menu.html.erb │ │ ├── _attributes.html.erb │ │ ├── _edit.html.erb │ │ ├── _form.html.erb │ │ ├── _form_add_team_member.html.erb │ │ ├── _form_update.html.erb │ │ ├── _history.html.erb │ │ ├── _joined_by.html.erb │ │ ├── _list.html.erb │ │ ├── _list_simple.html.erb │ │ ├── _list_very_simple.html.erb │ │ ├── _relations.html.erb │ │ ├── _sidebar.html.erb │ │ ├── calendar.html.erb │ │ ├── changes.rxml │ │ ├── context_menu.html.erb │ │ ├── edit.html.erb │ │ ├── gantt.html.erb │ │ ├── index.html.erb │ │ ├── move.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── journals/ │ │ ├── _notes_form.html.erb │ │ ├── edit.rjs │ │ └── update.rjs │ ├── layouts/ │ │ ├── _account_menu.html.erb │ │ ├── activescaffold.html.erb │ │ ├── admin.html.erb │ │ ├── base.html.erb │ │ ├── blank.html.erb │ │ ├── gooey.html.erb │ │ ├── help_sections.html.erb │ │ ├── issue_blank.html.erb │ │ ├── mailer.text.html.erb │ │ ├── mailer.text.plain.erb │ │ └── static.html.erb │ ├── mailer/ │ │ ├── _issue_text_html.html.erb │ │ ├── _issue_text_plain.html.erb │ │ ├── account_activated.text.html.html.erb │ │ ├── account_activated.text.plain.html.erb │ │ ├── account_activation_request.text.html.html.erb │ │ ├── account_activation_request.text.plain.html.erb │ │ ├── account_information.text.html.html.erb │ │ ├── account_information.text.plain.html.erb │ │ ├── attachments_added.text.html.html.erb │ │ ├── attachments_added.text.plain.html.erb │ │ ├── daily_digest.text.html.html.erb │ │ ├── daily_digest.text.plain.html.erb │ │ ├── document_added.text.html.html.erb │ │ ├── document_added.text.plain.html.erb │ │ ├── email_update_activation.text.html.html.erb │ │ ├── email_update_activation.text.plain.html.erb │ │ ├── invitation_add.text.html.html.erb │ │ ├── invitation_add.text.plain.html.erb │ │ ├── invitation_remind.text.html.html.erb │ │ ├── invitation_remind.text.plain.html.erb │ │ ├── issue_add.text.html.html.erb │ │ ├── issue_add.text.plain.html.erb │ │ ├── issue_edit.text.html.html.erb │ │ ├── issue_edit.text.plain.html.erb │ │ ├── lost_password.text.html.html.erb │ │ ├── lost_password.text.plain.html.erb │ │ ├── message_posted.text.html.html.erb │ │ ├── message_posted.text.plain.html.erb │ │ ├── news_added.text.html.html.erb │ │ ├── news_added.text.plain.html.erb │ │ ├── personal_welcome.text.html.html.erb │ │ ├── personal_welcome.text.plain.html.erb │ │ ├── register.text.html.html.erb │ │ ├── register.text.plain.html.erb │ │ ├── reminder.text.html.html.erb │ │ ├── reminder.text.plain.html.erb │ │ ├── test.text.html.html.erb │ │ ├── test.text.plain.html.erb │ │ ├── wiki_content_added.text.html.html.erb │ │ ├── wiki_content_added.text.plain.html.erb │ │ ├── wiki_content_updated.text.html.html.erb │ │ └── wiki_content_updated.text.plain.html.erb │ ├── mails/ │ │ ├── _inbox.html.erb │ │ ├── _sent.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── members/ │ │ └── autocomplete_for_member.html.erb │ ├── messages/ │ │ ├── _form.html.erb │ │ ├── _motion_topic.html.erb │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── motion_votes/ │ │ ├── _vote.html.erb │ │ ├── cast_vote.js.rjs │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── motions/ │ │ ├── _motions.html.erb │ │ ├── edit.html.erb │ │ ├── eligible_users.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── my/ │ │ ├── _belong_to_projects.html.erb │ │ ├── _billing_form.html.erb │ │ ├── _block.html.erb │ │ ├── _my_projects.html.erb │ │ ├── _plan_description.html.erb │ │ ├── _plan_descriptions.html.erb │ │ ├── _project_list.html.erb │ │ ├── _sidebar.html.erb │ │ ├── _usage_stats.html.erb │ │ ├── account.html.erb │ │ ├── blocks/ │ │ │ ├── _calendar.html.erb │ │ │ ├── _documents.html.erb │ │ │ ├── _issuesassignedtome.html.erb │ │ │ ├── _issuesreportedbyme.html.erb │ │ │ ├── _issueswatched.html.erb │ │ │ └── _news.html.erb │ │ ├── issues.html.erb │ │ ├── page.html.erb │ │ ├── page_layout.html.erb │ │ ├── password.html.erb │ │ ├── projects.html.erb │ │ └── upgrade.html.erb │ ├── news/ │ │ ├── _form.html.erb │ │ ├── _news.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── notifications/ │ │ ├── _credits_distributed.erb │ │ ├── _credits_transferred.html.erb │ │ ├── _invitation.erb │ │ ├── _issue_joined.html.erb │ │ ├── _issue_left.html.erb │ │ ├── _issue_team_member_added.html.erb │ │ ├── _issue_team_member_removed.html.erb │ │ ├── _mention.html.erb │ │ ├── _message.html.erb │ │ ├── _motion_started.erb │ │ ├── _new_role.erb │ │ ├── _retro_ended.erb │ │ ├── _retro_started.erb │ │ ├── _trial_expired.html.erb │ │ ├── _unresponded.html.erb │ │ ├── _usage_over.html.erb │ │ ├── edit.html.erb │ │ ├── error.js.rjs │ │ ├── hide.js.rjs │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── projects/ │ │ ├── _active_member_box.html.erb │ │ ├── _active_member_box_simple.html.erb │ │ ├── _clearance_member_box.html.erb │ │ ├── _dashboard_javascript_variables.html.erb │ │ ├── _edit.html.erb │ │ ├── _form.html.erb │ │ ├── _member_box.html.erb │ │ ├── _point_scale.html.erb │ │ ├── _project_list.html.erb │ │ ├── _project_summary.html.erb │ │ ├── _projects_list_simple.html.erb │ │ ├── _subprojects.html.erb │ │ ├── _team_link.html.erb │ │ ├── _team_list.html.erb │ │ ├── _team_list_member.html.erb │ │ ├── activity.html.erb │ │ ├── add.html.erb │ │ ├── add_file.html.erb │ │ ├── copy.html.erb │ │ ├── core_vote.js.rjs │ │ ├── credits.html.erb │ │ ├── dashboard.html.erb │ │ ├── index.html.erb │ │ ├── list_files.html.erb │ │ ├── list_members.html.erb │ │ ├── map.html.erb │ │ ├── move.html.erb │ │ ├── overview.html.erb │ │ ├── settings/ │ │ │ ├── _boards.html.erb │ │ │ ├── _hourly_types.html.erb │ │ │ ├── _members.html.erb │ │ │ ├── _modules.html.erb │ │ │ ├── _select_new_member.html.erb │ │ │ └── _wiki.html.erb │ │ ├── settings.html.erb │ │ ├── show.png.flexi │ │ ├── team.html.erb │ │ └── team_update.js.rjs │ ├── queries/ │ │ ├── _columns.html.erb │ │ ├── _filters.html.erb │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── new.html.erb │ ├── quotes/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── reports/ │ │ ├── _details.html.erb │ │ ├── _simple.html.erb │ │ ├── issue_report.html.erb │ │ └── issue_report_details.html.erb │ ├── reputations/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── retro_ratings/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── retros/ │ │ ├── _issue_details.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show_multiple.html.erb │ ├── roles/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── list.html.erb │ │ ├── new.html.erb │ │ └── report.html.erb │ ├── search/ │ │ └── index.html.erb │ ├── settings/ │ │ ├── _authentication.html.erb │ │ ├── _display.html.erb │ │ ├── _general.html.erb │ │ ├── _issues.html.erb │ │ ├── _mail_handler.html.erb │ │ ├── _notifications.html.erb │ │ ├── _projects.html.erb │ │ ├── edit.html.erb │ │ └── plugin.html.erb │ ├── shares/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── todos/ │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── trackers/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── list.html.erb │ │ └── new.html.erb │ ├── users/ │ │ ├── _activity.html.erb │ │ ├── _form.html.erb │ │ ├── _general.html.erb │ │ ├── _general_info.html.erb │ │ ├── _membership.html.erb │ │ ├── _memberships.html.erb │ │ ├── _reputation.html.erb │ │ ├── add.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── watchers/ │ │ └── _watchers.html.erb │ ├── welcome/ │ │ ├── _list_workstreams.html.erb │ │ ├── index.html.erb │ │ └── robots.html.erb │ ├── wiki/ │ │ ├── _content.html.erb │ │ ├── _sidebar.html.erb │ │ ├── annotate.html.erb │ │ ├── destroy.html.erb │ │ ├── diff.html.erb │ │ ├── edit.html.erb │ │ ├── export.html.erb │ │ ├── export_multiple.html.erb │ │ ├── history.html.erb │ │ ├── rename.html.erb │ │ ├── show.html.erb │ │ ├── special_date_index.html.erb │ │ └── special_page_index.html.erb │ ├── wikis/ │ │ └── destroy.html.erb │ └── workflows/ │ ├── _action_menu.html.erb │ ├── copy.html.erb │ ├── edit.html.erb │ └── index.html.erb ├── config/ │ ├── additional_environment.rb.example │ ├── boot.rb │ ├── database.yml.example │ ├── email.yml.example │ ├── environment.rb │ ├── environments/ │ │ ├── cucumber.rb │ │ ├── demo.rb │ │ ├── development.rb │ │ ├── production.rb │ │ ├── selenium.rb │ │ ├── test.rb │ │ ├── test_pgsql.rb │ │ └── test_sqlite3.rb │ ├── exceptional.yml │ ├── initializers/ │ │ ├── 10-patches.rb │ │ ├── 20-mime_types.rb │ │ ├── 30-redmine.rb │ │ ├── 40-email.rb │ │ ├── activity_streams.rb │ │ ├── admin_data.rb │ │ ├── aws_s3.rb │ │ ├── backtrace_silencers.rb │ │ ├── bigdecimal-segfault-fix.rb │ │ ├── hash.rb │ │ ├── inflections.rb │ │ ├── recurly_config.rb │ │ └── timeout.rb │ ├── locales/ │ │ └── en.yml │ ├── preinitializer.rb │ ├── rails_best_practices.yml │ ├── routes.rb │ ├── s3.yml │ ├── selenium.yml │ └── settings.yml ├── cucumber.yml ├── db/ │ ├── migrate/ │ │ ├── 20110320055526_acts_as_taggable_on_migration.rb │ │ ├── 20110329230314_add_projectid_to_taggable.rb │ │ └── 20110330041648_add_tags_to_issue.rb │ ├── schema.rb │ └── seeds.rb ├── deploy ├── doc/ │ ├── COPYING │ ├── INSTALL │ ├── README_FOR_APP │ └── RUNNING_TESTS ├── extra/ │ ├── mail_handler/ │ │ └── rdm-mailhandler.rb │ └── sample_plugin/ │ ├── README │ ├── app/ │ │ ├── controllers/ │ │ │ └── example_controller.rb │ │ ├── models/ │ │ │ └── meeting.rb │ │ └── views/ │ │ ├── example/ │ │ │ ├── say_goodbye.html.erb │ │ │ └── say_hello.html.erb │ │ ├── my/ │ │ │ └── blocks/ │ │ │ └── _sample_block.html.erb │ │ └── settings/ │ │ └── _sample_plugin_settings.html.erb │ ├── assets/ │ │ └── stylesheets/ │ │ └── example.css │ ├── config/ │ │ └── locales/ │ │ ├── en.yml │ │ └── fr.yml │ ├── db/ │ │ └── migrate/ │ │ └── 001_create_meetings.rb │ └── init.rb ├── lib/ │ ├── activity_streams/ │ │ ├── log_activity_streams.rb │ │ └── routes.rb │ ├── activity_streams.rb │ ├── ar_condition.rb │ ├── diff.rb │ ├── faster_csv.rb │ ├── float.rb │ ├── generators/ │ │ ├── redmine_plugin/ │ │ │ ├── USAGE │ │ │ ├── redmine_plugin_generator.rb │ │ │ └── templates/ │ │ │ ├── README.rdoc │ │ │ ├── en.yml │ │ │ ├── en_rails_i18n.yml │ │ │ ├── init.rb.erb │ │ │ └── test_helper.rb.erb │ │ ├── redmine_plugin_controller/ │ │ │ ├── USAGE │ │ │ ├── redmine_plugin_controller_generator.rb │ │ │ └── templates/ │ │ │ ├── controller.rb.erb │ │ │ ├── functional_test.rb.erb │ │ │ ├── helper.rb.erb │ │ │ └── view.html.erb │ │ └── redmine_plugin_model/ │ │ ├── USAGE │ │ ├── redmine_plugin_model_generator.rb │ │ └── templates/ │ │ ├── fixtures.yml │ │ ├── migration.rb.erb │ │ ├── model.rb.erb │ │ └── unit_test.rb.erb │ ├── mention.rb │ ├── redcloth3.rb │ ├── redmine/ │ │ ├── about.rb │ │ ├── access_control.rb │ │ ├── access_keys.rb │ │ ├── activity/ │ │ │ └── fetcher.rb │ │ ├── activity.rb │ │ ├── core_ext/ │ │ │ ├── string/ │ │ │ │ ├── conversions.rb │ │ │ │ └── inflections.rb │ │ │ └── string.rb │ │ ├── core_ext.rb │ │ ├── default_data/ │ │ │ └── loader.rb │ │ ├── export/ │ │ │ └── pdf.rb │ │ ├── helpers/ │ │ │ ├── calendar.rb │ │ │ └── gantt.rb │ │ ├── i18n.rb │ │ ├── imap.rb │ │ ├── info.rb │ │ ├── menu_manager.rb │ │ ├── mime_type.rb │ │ ├── platform.rb │ │ ├── plugin.rb │ │ ├── search.rb │ │ ├── themes.rb │ │ ├── unified_diff.rb │ │ ├── utils.rb │ │ ├── version.rb │ │ ├── views/ │ │ │ ├── my_page/ │ │ │ │ └── block.rb │ │ │ └── other_formats_builder.rb │ │ ├── wiki_formatting/ │ │ │ ├── macros.rb │ │ │ └── textile/ │ │ │ ├── formatter.rb │ │ │ └── helper.rb │ │ └── wiki_formatting.rb │ ├── redmine.rb │ ├── string.rb │ ├── tabular_form_builder.rb │ └── tasks/ │ ├── autoaccept_commitrequests.rake │ ├── backup.rake │ ├── bootstrap.rake │ ├── cleanup.rake │ ├── close_retros.rake │ ├── cron.rake │ ├── cucumber.rake │ ├── custom.rake │ ├── deprecated.rake │ ├── email.rake │ ├── extract_fixtures.rake │ ├── fetch_changesets.rake │ ├── initializers.rake │ ├── load_default_data.rake │ ├── locales.rake │ ├── metrics.rake │ ├── migrate_from_mantis.rake │ ├── migrate_from_trac.rake │ ├── migrate_plugins.rake │ ├── plugins.rake │ ├── reek.rake │ ├── reminder.rake │ ├── remove_problem_type.rake │ ├── reset_all_passwords.rake │ ├── rspec.rake │ ├── steak.rake │ ├── testing.rake │ ├── update_item_statuses.rake │ └── watchers.rake ├── public/ │ ├── .htaccess │ ├── 404.html │ ├── 500.html │ ├── help/ │ │ ├── wiki_syntax.html │ │ └── wiki_syntax_detailed.html │ ├── images/ │ │ ├── gallery/ │ │ │ └── index.sof │ │ ├── inner-background.psd │ │ └── static/ │ │ ├── HomePageMain.psd │ │ ├── Untitled-1.psd │ │ ├── Untitled-2.psd │ │ ├── call-us.psd │ │ ├── col-img3.psd │ │ ├── index.sof │ │ ├── landingmockup.psd │ │ ├── pricings-buttom.psd │ │ ├── thanks.psd │ │ └── videotemplate.psd │ ├── javascripts/ │ │ ├── application.js │ │ ├── calendar/ │ │ │ ├── calendar-setup.js │ │ │ ├── calendar.js │ │ │ └── lang/ │ │ │ ├── calendar-bg.js │ │ │ ├── calendar-bs.js │ │ │ ├── calendar-ca.js │ │ │ ├── calendar-cs.js │ │ │ ├── calendar-da.js │ │ │ ├── calendar-de.js │ │ │ ├── calendar-en.js │ │ │ ├── calendar-es.js │ │ │ ├── calendar-fi.js │ │ │ ├── calendar-fr.js │ │ │ ├── calendar-gl.js │ │ │ ├── calendar-he.js │ │ │ ├── calendar-hu.js │ │ │ ├── calendar-id.js │ │ │ ├── calendar-it.js │ │ │ ├── calendar-ja.js │ │ │ ├── calendar-ko.js │ │ │ ├── calendar-lt.js │ │ │ ├── calendar-mk.js │ │ │ ├── calendar-nl.js │ │ │ ├── calendar-no.js │ │ │ ├── calendar-pl.js │ │ │ ├── calendar-pt-br.js │ │ │ ├── calendar-pt.js │ │ │ ├── calendar-ro.js │ │ │ ├── calendar-ru.js │ │ │ ├── calendar-sk.js │ │ │ ├── calendar-sl.js │ │ │ ├── calendar-sr.js │ │ │ ├── calendar-sv.js │ │ │ ├── calendar-th.js │ │ │ ├── calendar-tr.js │ │ │ ├── calendar-uk.js │ │ │ ├── calendar-vi.js │ │ │ ├── calendar-zh-tw.js │ │ │ └── calendar-zh.js │ │ ├── context_menu.js │ │ ├── controls.js │ │ ├── dashboard.js │ │ ├── dragdrop.js │ │ ├── effects.js │ │ ├── enterprise_map.js │ │ ├── fancybox/ │ │ │ ├── index.sof │ │ │ ├── jquery.fancybox-1.3.0.css │ │ │ ├── jquery.fancybox-1.3.0.pack.js │ │ │ └── jquery.mousewheel-3.0.2.pack.js │ │ ├── fileuploader.js │ │ ├── hoverIntent.js │ │ ├── issue.js │ │ ├── jQuery.bubbletip-1.0.4.js │ │ ├── jScrollPane │ │ ├── jScrollPane.js │ │ ├── jit-yc.js │ │ ├── jit.js │ │ ├── jquery-ui.js │ │ ├── jquery.autocomplete.js │ │ ├── jquery.autocomplete.pack.js │ │ ├── jquery.easing-1.3.pack.js │ │ ├── jquery.em.js │ │ ├── jquery.fancybox-1.3.0.js │ │ ├── jquery.fancybox-1.3.0.pack.js │ │ ├── jquery.fancybox-1.3.4.js │ │ ├── jquery.fancybox-1.3.4.pack.js │ │ ├── jquery.fileupload-ui.js │ │ ├── jquery.fileupload.js │ │ ├── jquery.jgrowl_minimized.js │ │ ├── jquery.js │ │ ├── jquery.mousewheel-3.0.2.pack.js │ │ ├── jquery.mousewheel-3.0.4.pack.js │ │ ├── jquery.mousewheel.js │ │ ├── jquery.scrollTo-min.js │ │ ├── jquery.tagsinput.js │ │ ├── jquery.ui.autocomplete.ext.js │ │ ├── jquery.ui.autocomplete.js │ │ ├── jquery.ui.position.js │ │ ├── jrails.js │ │ ├── json2.js │ │ ├── jstoolbar/ │ │ │ ├── jstoolbar.js │ │ │ ├── lang/ │ │ │ │ ├── jstoolbar-bg.js │ │ │ │ ├── jstoolbar-bs.js │ │ │ │ ├── jstoolbar-ca.js │ │ │ │ ├── jstoolbar-cs.js │ │ │ │ ├── jstoolbar-da.js │ │ │ │ ├── jstoolbar-de.js │ │ │ │ ├── jstoolbar-en.js │ │ │ │ ├── jstoolbar-es.js │ │ │ │ ├── jstoolbar-fi.js │ │ │ │ ├── jstoolbar-fr.js │ │ │ │ ├── jstoolbar-gl.js │ │ │ │ ├── jstoolbar-he.js │ │ │ │ ├── jstoolbar-hu.js │ │ │ │ ├── jstoolbar-id.js │ │ │ │ ├── jstoolbar-it.js │ │ │ │ ├── jstoolbar-ja.js │ │ │ │ ├── jstoolbar-ko.js │ │ │ │ ├── jstoolbar-lt.js │ │ │ │ ├── jstoolbar-mk.js │ │ │ │ ├── jstoolbar-nl.js │ │ │ │ ├── jstoolbar-no.js │ │ │ │ ├── jstoolbar-pl.js │ │ │ │ ├── jstoolbar-pt-br.js │ │ │ │ ├── jstoolbar-pt.js │ │ │ │ ├── jstoolbar-ro.js │ │ │ │ ├── jstoolbar-ru.js │ │ │ │ ├── jstoolbar-sk.js │ │ │ │ ├── jstoolbar-sl.js │ │ │ │ ├── jstoolbar-sr.js │ │ │ │ ├── jstoolbar-sv.js │ │ │ │ ├── jstoolbar-th.js │ │ │ │ ├── jstoolbar-tr.js │ │ │ │ ├── jstoolbar-uk.js │ │ │ │ ├── jstoolbar-vi.js │ │ │ │ ├── jstoolbar-zh-tw.js │ │ │ │ └── jstoolbar-zh.js │ │ │ └── textile.js │ │ ├── lightbox.js │ │ ├── prototype.js │ │ ├── repository_navigation.js │ │ ├── retro.js │ │ ├── select_list_move.js │ │ ├── static/ │ │ │ ├── Quicksand_Book_400.font.js │ │ │ ├── css_browser_selector.js │ │ │ ├── cufon-yui.js │ │ │ ├── execute.js │ │ │ ├── hoverIntent.js │ │ │ ├── index.sof │ │ │ ├── jquery-galleryview-1.1/ │ │ │ │ ├── index.sof │ │ │ │ ├── jquery.galleryview-1.1.js │ │ │ │ └── jquery.timers-1.1.2.js │ │ │ ├── jquery.easing.1.3.js │ │ │ ├── jquery.scroll.pack.js │ │ │ ├── jquery.send.js │ │ │ └── superfish.js │ │ ├── store.js │ │ ├── superfish.js │ │ ├── supersubs.js │ │ ├── textile-editor-config.js │ │ └── textile-editor.js │ ├── maintenance/ │ │ └── index.html │ ├── stylesheets/ │ │ ├── bubbletip-IE.css │ │ ├── bubbletip.css │ │ ├── calendar.css │ │ ├── context_menu.css │ │ ├── csshover.htc │ │ ├── custom.css │ │ ├── dashboard.css │ │ ├── enterprise_map.css │ │ ├── fileuploader.css │ │ ├── gt-fixed.css │ │ ├── gt-rounded-corners.css │ │ ├── gt-styles.css │ │ ├── headerandfooter.css │ │ ├── issue.css │ │ ├── jScrollPane.css │ │ ├── jquery-ui-1.7.2.custom.css │ │ ├── jquery-ui-1.8.8.custom.css │ │ ├── jquery.autocomplete.css │ │ ├── jquery.fancybox-1.3.0.css │ │ ├── jquery.fancybox-1.3.4.css │ │ ├── jquery.fileupload-ui.css │ │ ├── jquery.tagsinput.css │ │ ├── jquery.ui.autocomplete.css │ │ ├── jstoolbar.css │ │ ├── lightbox.css │ │ ├── oldapplication.css │ │ ├── override.css │ │ ├── reset-fonts.css │ │ ├── scaffold.css │ │ ├── scm.css │ │ ├── static/ │ │ │ ├── index.sof │ │ │ ├── print.css │ │ │ ├── screen.css │ │ │ ├── style.css │ │ │ └── superfish.css │ │ └── textile-editor.css │ └── themes/ │ ├── README │ └── bettermeans/ │ └── stylesheets/ │ └── application.css ├── script/ │ ├── about │ ├── autospec │ ├── breakpointer │ ├── console │ ├── cucumber │ ├── dbconsole │ ├── delayed_job │ ├── destroy │ ├── generate │ ├── performance/ │ │ ├── benchmarker │ │ ├── profiler │ │ └── request │ ├── plugin │ ├── process/ │ │ ├── inspector │ │ ├── reaper │ │ ├── spawner │ │ └── spinner │ ├── runner │ ├── server │ └── spec ├── spec/ │ ├── acceptance/ │ │ ├── acceptance_helper.rb │ │ ├── support/ │ │ │ ├── helpers.rb │ │ │ └── paths.rb │ │ └── user_login_spec.rb │ ├── controllers/ │ │ ├── account_controller_spec.rb │ │ └── projects/ │ │ ├── index_active_spec.rb │ │ ├── index_latest_spec.rb │ │ ├── index_spec.rb │ │ ├── move_spec.rb │ │ └── new_dash_data_spec.rb │ ├── factories.rb │ ├── lib/ │ │ └── hash_spec.rb │ ├── models/ │ │ └── project_spec.rb │ ├── rcov.opts │ ├── routing/ │ │ ├── accounts_routing_spec.rb │ │ ├── activity_stream_preferences_routing_spec.rb │ │ ├── activity_streams_routing_spec.rb │ │ ├── admin_routing_spec.rb │ │ ├── attachments_routing_spec.rb │ │ ├── auth_sources_routing_spec.rb │ │ ├── boards_routing_spec.rb │ │ ├── comments_routing_spec.rb │ │ ├── credit_distributions_routing_spec.rb │ │ ├── credit_transfers_routing_spec.rb │ │ ├── credits_routing_spec.rb │ │ ├── documents_routing_spec.rb │ │ ├── email_updates_routing_spec.rb │ │ ├── enterprises_routing_spec.rb │ │ ├── enumerations_routing_spec.rb │ │ ├── help_routing_spec.rb │ │ ├── help_sections_routing_spec.rb │ │ ├── home_routing_spec.rb │ │ ├── hourly_types_routing_spec.rb │ │ ├── invitations_routing_spec.rb │ │ ├── issue_invitations_routing_spec.rb │ │ ├── issue_relations_routing_spec.rb │ │ ├── issue_statuses_routing_spec.rb │ │ ├── issue_votes_routing_spec.rb │ │ ├── issues_routing_spec.rb │ │ ├── journals_routing_spec.rb │ │ ├── mail_handler_routing_spec.rb │ │ ├── mails_routing_spec.rb │ │ ├── members_routing_spec.rb │ │ ├── messages_routing_spec.rb │ │ ├── motion_votes_routing_spec.rb │ │ ├── motions_routing_spec.rb │ │ ├── my_routing_spec.rb │ │ ├── news_routing_spec.rb │ │ ├── notifications_routing_spec.rb │ │ ├── projects_routing_spec.rb │ │ ├── queries_routing_spec.rb │ │ ├── quotes_routing_spec.rb │ │ ├── recurly_notifications_routing_spec.rb │ │ ├── reports_routing_spec.rb │ │ ├── reputations_routing_spec.rb │ │ ├── retro_ratings_routing_spec.rb │ │ ├── retros_routing_spec.rb │ │ ├── roles_routing_spec.rb │ │ ├── search_routing_spec.rb │ │ ├── settings_routing_spec.rb │ │ ├── shares_routing_spec.rb │ │ ├── todos_routing_spec.rb │ │ ├── trackers_routing_spec.rb │ │ ├── users_routing_spec.rb │ │ ├── votes_routing_spec.rb │ │ ├── watchers_routing_spec.rb │ │ ├── welcome_routing_spec.rb │ │ ├── wiki_routing_spec.rb │ │ ├── wikis_routing_spec.rb │ │ └── workflows_routing_spec.rb │ ├── spec.opts │ └── spec_helper.rb └── vendor/ └── plugins/ ├── acts-as-taggable-on/ │ ├── .rspec │ ├── CHANGELOG │ ├── Gemfile │ ├── MIT-LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── VERSION │ ├── acts-as-taggable-on.gemspec │ ├── generators/ │ │ └── acts_as_taggable_on_migration/ │ │ ├── acts_as_taggable_on_migration_generator.rb │ │ └── templates/ │ │ └── migration.rb │ ├── lib/ │ │ ├── acts-as-taggable-on.rb │ │ ├── acts_as_taggable_on/ │ │ │ ├── acts_as_tagger.rb │ │ │ ├── compatibility/ │ │ │ │ ├── Gemfile │ │ │ │ └── active_record_backports.rb │ │ │ ├── tag.rb │ │ │ ├── tag_list.rb │ │ │ ├── taggable/ │ │ │ │ ├── cache.rb │ │ │ │ ├── collection.rb │ │ │ │ ├── core.rb │ │ │ │ ├── ownership.rb │ │ │ │ └── related.rb │ │ │ ├── taggable.rb │ │ │ ├── tagging.rb │ │ │ └── tags_helper.rb │ │ └── generators/ │ │ └── acts_as_taggable_on/ │ │ └── migration/ │ │ ├── migration_generator.rb │ │ └── templates/ │ │ └── active_record/ │ │ └── migration.rb │ ├── rails/ │ │ └── init.rb │ ├── spec/ │ │ ├── acts_as_taggable_on/ │ │ │ ├── acts_as_taggable_on_spec.rb │ │ │ ├── acts_as_tagger_spec.rb │ │ │ ├── tag_list_spec.rb │ │ │ ├── tag_spec.rb │ │ │ ├── taggable_spec.rb │ │ │ ├── tagger_spec.rb │ │ │ ├── tagging_spec.rb │ │ │ └── tags_helper_spec.rb │ │ ├── bm.rb │ │ ├── database.yml.sample │ │ ├── models.rb │ │ ├── schema.rb │ │ └── spec_helper.rb │ └── uninstall.rb ├── acts_as_attachable/ │ ├── init.rb │ └── lib/ │ └── acts_as_attachable.rb ├── acts_as_event/ │ ├── init.rb │ └── lib/ │ └── acts_as_event.rb ├── acts_as_list/ │ ├── README │ ├── init.rb │ ├── lib/ │ │ └── active_record/ │ │ └── acts/ │ │ └── list.rb │ └── test/ │ └── list_test.rb ├── acts_as_searchable/ │ ├── init.rb │ └── lib/ │ └── acts_as_searchable.rb ├── acts_as_tree/ │ ├── README │ ├── Rakefile │ ├── init.rb │ ├── lib/ │ │ └── active_record/ │ │ └── acts/ │ │ └── tree.rb │ └── test/ │ ├── abstract_unit.rb │ ├── acts_as_tree_test.rb │ ├── database.yml │ ├── fixtures/ │ │ ├── mixin.rb │ │ └── mixins.yml │ └── schema.rb ├── acts_as_versioned/ │ ├── CHANGELOG │ ├── MIT-LICENSE │ ├── README │ ├── RUNNING_UNIT_TESTS │ ├── Rakefile │ ├── init.rb │ ├── lib/ │ │ └── acts_as_versioned.rb │ └── test/ │ ├── abstract_unit.rb │ ├── database.yml │ ├── fixtures/ │ │ ├── authors.yml │ │ ├── landmark.rb │ │ ├── landmark_versions.yml │ │ ├── landmarks.yml │ │ ├── locked_pages.yml │ │ ├── locked_pages_revisions.yml │ │ ├── migrations/ │ │ │ └── 1_add_versioned_tables.rb │ │ ├── page.rb │ │ ├── page_versions.yml │ │ ├── pages.yml │ │ └── widget.rb │ ├── migration_test.rb │ ├── schema.rb │ └── versioned_test.rb ├── acts_as_watchable/ │ ├── init.rb │ └── lib/ │ └── acts_as_watchable.rb ├── admin_data/ │ ├── History.txt │ ├── README.md │ ├── Rakefile │ ├── app/ │ │ ├── controllers/ │ │ │ └── admin_data/ │ │ │ ├── base_controller.rb │ │ │ ├── diagnostic_controller.rb │ │ │ ├── feed_controller.rb │ │ │ ├── main_controller.rb │ │ │ ├── migration_controller.rb │ │ │ ├── search_controller.rb │ │ │ └── validate_model_controller.rb │ │ └── views/ │ │ ├── admin_data/ │ │ │ ├── diagnostic/ │ │ │ │ ├── index.html.erb │ │ │ │ └── missing_index.html.erb │ │ │ ├── feed/ │ │ │ │ └── index.rss.builder │ │ │ ├── main/ │ │ │ │ ├── all_models.html.erb │ │ │ │ ├── association/ │ │ │ │ │ ├── _association_info.html.erb │ │ │ │ │ ├── _belongs_to_info.html.erb │ │ │ │ │ ├── _has_many_info.html.erb │ │ │ │ │ └── _has_one_info.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── misc/ │ │ │ │ │ ├── _form.html.erb │ │ │ │ │ └── _modify_record.html.erb │ │ │ │ ├── new.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ └── table_structure.html.erb │ │ │ ├── migration/ │ │ │ │ ├── index.html.erb │ │ │ │ └── jstest.html.erb │ │ │ ├── search/ │ │ │ │ ├── _search_base.html.erb │ │ │ │ ├── advance_search.html.erb │ │ │ │ ├── quick_search.html.erb │ │ │ │ └── search/ │ │ │ │ ├── _advance_search_form.html.erb │ │ │ │ ├── _errors.html.erb │ │ │ │ ├── _listing.html.erb │ │ │ │ ├── _search_form.html.erb │ │ │ │ ├── _sortby.html.erb │ │ │ │ └── _title.html.erb │ │ │ ├── shared/ │ │ │ │ ├── _breadcrum.html.erb │ │ │ │ ├── _drop_down_klasses.html.erb │ │ │ │ ├── _flash_message.html.erb │ │ │ │ ├── _header.html.erb │ │ │ │ ├── _powered_by.html.erb │ │ │ │ └── _secondary_navigation.html.erb │ │ │ └── validate_model/ │ │ │ ├── _bad.html.erb │ │ │ ├── tid.html.erb │ │ │ └── validate.html.erb │ │ └── layouts/ │ │ └── admin_data.html.erb │ ├── config/ │ │ └── routes.rb │ ├── doc/ │ │ └── git.txt │ ├── init.rb │ ├── lib/ │ │ ├── admin_data/ │ │ │ ├── chelper.rb │ │ │ ├── compatibility.rb │ │ │ ├── helpers.rb │ │ │ ├── rake_util.rb │ │ │ ├── search.rb │ │ │ ├── settings.rb │ │ │ └── util.rb │ │ ├── admin_data_date_validation.rb │ │ ├── css/ │ │ │ ├── app.css │ │ │ ├── base.css │ │ │ ├── header.css │ │ │ ├── rounded.css │ │ │ ├── themes/ │ │ │ │ └── drastic-dark/ │ │ │ │ └── style.css │ │ │ ├── umbrella.css │ │ │ └── vendor/ │ │ │ ├── jquery-ui-1.7.2.custom.css │ │ │ └── qunit.css │ │ └── js/ │ │ ├── advance_search/ │ │ │ ├── act_on_result.js │ │ │ ├── adv_search.js │ │ │ ├── advance_search.js │ │ │ ├── advance_search_structure.js │ │ │ ├── ajaxify_advance_search.js │ │ │ ├── build_first_row.js │ │ │ ├── event_bindings.js │ │ │ ├── global_ajax_setting.js │ │ │ └── trigger_submit_on_domready.js │ │ ├── misc/ │ │ │ ├── drop_down_change.js │ │ │ ├── js_util.js │ │ │ └── quick_search_input_focus.js │ │ ├── test/ │ │ │ ├── act_on_result.js │ │ │ ├── advance_search.js │ │ │ ├── ajaxify_advance_search.js │ │ │ ├── build_first_row.js │ │ │ └── event_bindings.js │ │ ├── validate_model/ │ │ │ ├── ajaxify_form.js │ │ │ └── select_all.js │ │ └── vendor/ │ │ ├── jack.js │ │ ├── jquery-1.4.1.js │ │ ├── jquery.ba-isjquery.js │ │ ├── jquery.form.js │ │ ├── jquery.lint.js │ │ ├── log.js │ │ └── qunit.js │ ├── tasks/ │ │ ├── admin_data_tasks.rake │ │ └── validate_models_bg.rake │ └── test/ │ ├── factories/ │ │ ├── article.rb │ │ ├── car.rb │ │ ├── city.rb │ │ ├── comment.rb │ │ ├── door.rb │ │ └── engine.rb │ ├── functional/ │ │ ├── base_controller_test.rb │ │ ├── feed_controller_test.rb │ │ ├── main_controller_test.rb │ │ ├── migration_controller_test.rb │ │ └── search_controller_test.rb │ ├── helper/ │ │ └── view_helper_test.rb │ ├── misc_tests/ │ │ ├── date_validation_test.rb │ │ ├── settings_test.rb │ │ └── util_test.rb │ ├── rails_root/ │ │ ├── Rakefile │ │ ├── app/ │ │ │ ├── controllers/ │ │ │ │ ├── application_controller.rb │ │ │ │ └── articles_controller.rb │ │ │ └── models/ │ │ │ ├── article.rb │ │ │ ├── city.rb │ │ │ ├── comment.rb │ │ │ ├── tech_magazine.rb │ │ │ └── vehicle/ │ │ │ ├── car.rb │ │ │ ├── door.rb │ │ │ └── engine.rb │ │ ├── config/ │ │ │ ├── boot.rb │ │ │ ├── database.yml │ │ │ ├── environment.rb │ │ │ ├── environments/ │ │ │ │ ├── development.rb │ │ │ │ └── test.rb │ │ │ ├── initializers/ │ │ │ │ └── new_rails_defaults.rb │ │ │ └── routes.rb │ │ ├── db/ │ │ │ ├── migrate/ │ │ │ │ └── 20090809061114_create_tables.rb │ │ │ └── schema.rb │ │ ├── public/ │ │ │ └── tmp/ │ │ │ └── response.txt │ │ └── script/ │ │ ├── about │ │ ├── console │ │ ├── dbconsole │ │ ├── destroy │ │ ├── generate │ │ ├── performance/ │ │ │ ├── benchmarker │ │ │ └── profiler │ │ ├── plugin │ │ ├── runner │ │ └── server │ └── test_helper.rb ├── annotate_models/ │ ├── History.txt │ ├── README.rdoc │ ├── Rakefile │ ├── VERSION.yml │ ├── annotate.gemspec │ ├── bin/ │ │ └── annotate │ ├── lib/ │ │ ├── annotate/ │ │ │ ├── annotate_models.rb │ │ │ ├── annotate_routes.rb │ │ │ └── tasks/ │ │ │ ├── migrate.rake │ │ │ └── migrate.rake~HEAD │ │ └── annotate.rb │ ├── spec/ │ │ ├── annotate/ │ │ │ ├── annotate_models_spec.rb │ │ │ └── annotate_routes_spec.rb │ │ ├── annotate_spec.rb │ │ ├── spec.opts │ │ └── spec_helper.rb │ └── todo.txt ├── auto_complete/ │ ├── README │ ├── Rakefile │ ├── init.rb │ ├── lib/ │ │ ├── auto_complete.rb │ │ └── auto_complete_macros_helper.rb │ └── test/ │ └── auto_complete_test.rb ├── awesome_nested_set/ │ ├── MIT-LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── awesome_nested_set.gemspec │ ├── init.rb │ ├── lib/ │ │ ├── awesome_nested_set/ │ │ │ ├── compatability.rb │ │ │ ├── helper.rb │ │ │ └── named_scope.rb │ │ └── awesome_nested_set.rb │ ├── rails/ │ │ └── init.rb │ └── test/ │ ├── awesome_nested_set/ │ │ └── helper_test.rb │ ├── awesome_nested_set_test.rb │ ├── db/ │ │ ├── database.yml │ │ └── schema.rb │ ├── fixtures/ │ │ ├── categories.yml │ │ ├── category.rb │ │ ├── departments.yml │ │ └── notes.yml │ └── test_helper.rb ├── classic_pagination/ │ ├── CHANGELOG │ ├── README │ ├── Rakefile │ ├── init.rb │ ├── install.rb │ ├── lib/ │ │ ├── pagination.rb │ │ └── pagination_helper.rb │ └── test/ │ ├── fixtures/ │ │ ├── companies.yml │ │ ├── company.rb │ │ ├── developer.rb │ │ ├── developers.yml │ │ ├── developers_projects.yml │ │ ├── project.rb │ │ ├── projects.yml │ │ ├── replies.yml │ │ ├── reply.rb │ │ ├── schema.sql │ │ ├── topic.rb │ │ └── topics.yml │ ├── helper.rb │ ├── pagination_helper_test.rb │ └── pagination_test.rb ├── coderay-0.7.6.227/ │ ├── FOLDERS │ ├── LICENSE │ ├── README │ ├── bin/ │ │ ├── coderay │ │ └── coderay_stylesheet │ └── lib/ │ ├── coderay/ │ │ ├── duo.rb │ │ ├── encoder.rb │ │ ├── encoders/ │ │ │ ├── _map.rb │ │ │ ├── count.rb │ │ │ ├── debug.rb │ │ │ ├── div.rb │ │ │ ├── html/ │ │ │ │ ├── css.rb │ │ │ │ ├── numerization.rb │ │ │ │ └── output.rb │ │ │ ├── html.rb │ │ │ ├── null.rb │ │ │ ├── page.rb │ │ │ ├── span.rb │ │ │ ├── statistic.rb │ │ │ ├── text.rb │ │ │ ├── tokens.rb │ │ │ ├── xml.rb │ │ │ └── yaml.rb │ │ ├── helpers/ │ │ │ ├── file_type.rb │ │ │ ├── gzip_simple.rb │ │ │ ├── plugin.rb │ │ │ └── word_list.rb │ │ ├── scanner.rb │ │ ├── scanners/ │ │ │ ├── _map.rb │ │ │ ├── c.rb │ │ │ ├── debug.rb │ │ │ ├── delphi.rb │ │ │ ├── html.rb │ │ │ ├── java.rb │ │ │ ├── javascript.rb │ │ │ ├── nitro_xhtml.rb │ │ │ ├── php.rb │ │ │ ├── plaintext.rb │ │ │ ├── rhtml.rb │ │ │ ├── ruby/ │ │ │ │ └── patterns.rb │ │ │ ├── ruby.rb │ │ │ ├── scheme.rb │ │ │ └── xml.rb │ │ ├── style.rb │ │ ├── styles/ │ │ │ ├── _map.rb │ │ │ ├── cycnus.rb │ │ │ └── murphy.rb │ │ ├── token_classes.rb │ │ └── tokens.rb │ └── coderay.rb ├── default_value_for/ │ ├── LICENSE.TXT │ ├── README.rdoc │ ├── Rakefile │ ├── init.rb │ └── test.rb ├── delayed_job/ │ ├── MIT-LICENSE │ ├── README.textile │ ├── Rakefile │ ├── VERSION │ ├── contrib/ │ │ └── delayed_job.monitrc │ ├── delayed_job.gemspec │ ├── generators/ │ │ └── delayed_job/ │ │ ├── delayed_job_generator.rb │ │ └── templates/ │ │ ├── migration.rb │ │ └── script │ ├── init.rb │ ├── lib/ │ │ ├── delayed/ │ │ │ ├── command.rb │ │ │ ├── job.rb │ │ │ ├── message_sending.rb │ │ │ ├── performable_method.rb │ │ │ ├── recipes.rb │ │ │ ├── tasks.rb │ │ │ └── worker.rb │ │ └── delayed_job.rb │ ├── recipes/ │ │ └── delayed_job.rb │ ├── spec/ │ │ ├── delayed_method_spec.rb │ │ ├── job_spec.rb │ │ ├── sample_jobs.rb │ │ ├── spec_helper.rb │ │ ├── story_spec.rb │ │ └── worker_spec.rb │ └── tasks/ │ └── jobs.rake ├── engines/ │ ├── .gitignore │ ├── CHANGELOG │ ├── MIT-LICENSE │ ├── README │ ├── Rakefile │ ├── about.yml │ ├── boot.rb │ ├── generators/ │ │ └── plugin_migration/ │ │ ├── USAGE │ │ ├── plugin_migration_generator.rb │ │ └── templates/ │ │ └── plugin_migration.erb │ ├── init.rb │ ├── lib/ │ │ ├── engines/ │ │ │ ├── assets.rb │ │ │ ├── plugin/ │ │ │ │ ├── list.rb │ │ │ │ ├── loader.rb │ │ │ │ ├── locator.rb │ │ │ │ └── migrator.rb │ │ │ ├── plugin.rb │ │ │ ├── rails_extensions/ │ │ │ │ ├── asset_helpers.rb │ │ │ │ ├── dependencies.rb │ │ │ │ ├── form_tag_helpers.rb │ │ │ │ ├── migrations.rb │ │ │ │ └── rails.rb │ │ │ ├── tasks/ │ │ │ │ ├── engines.rake │ │ │ │ └── engines.rake~HEAD │ │ │ └── testing.rb │ │ └── engines.rb │ └── test/ │ ├── app/ │ │ ├── controllers/ │ │ │ ├── app_and_plugin_controller.rb │ │ │ └── namespace/ │ │ │ └── app_and_plugin_controller.rb │ │ ├── helpers/ │ │ │ └── mail_helper.rb │ │ ├── models/ │ │ │ ├── app_and_plugin_model.rb │ │ │ └── notify_mail.rb │ │ ├── things/ │ │ │ └── thing.rb │ │ └── views/ │ │ ├── app_and_plugin/ │ │ │ └── a_view.html.erb │ │ ├── namespace/ │ │ │ └── app_and_plugin/ │ │ │ └── a_view.html.erb │ │ ├── notify_mail/ │ │ │ ├── implicit_multipart.text.html.erb │ │ │ ├── implicit_multipart.text.plain.erb │ │ │ ├── multipart_html.html.erb │ │ │ ├── multipart_plain.html.erb │ │ │ └── signup.text.plain.erb │ │ └── plugin_mail/ │ │ ├── mail_from_plugin_with_application_template.text.plain.erb │ │ └── multipart_from_plugin_with_application_template_plain.html.erb │ ├── functional/ │ │ ├── controller_loading_test.rb │ │ ├── exception_notification_compatibility_test.rb │ │ ├── locale_loading_test.rb │ │ ├── routes_test.rb │ │ ├── view_helpers_test.rb │ │ └── view_loading_test.rb │ ├── lib/ │ │ ├── app_and_plugin_lib_model.rb │ │ ├── engines_test_helper.rb │ │ └── render_information.rb │ ├── plugins/ │ │ ├── alpha_plugin/ │ │ │ ├── app/ │ │ │ │ ├── controllers/ │ │ │ │ │ ├── alpha_plugin_controller.rb │ │ │ │ │ ├── app_and_plugin_controller.rb │ │ │ │ │ ├── namespace/ │ │ │ │ │ │ ├── alpha_plugin_controller.rb │ │ │ │ │ │ ├── app_and_plugin_controller.rb │ │ │ │ │ │ └── shared_plugin_controller.rb │ │ │ │ │ └── shared_plugin_controller.rb │ │ │ │ ├── models/ │ │ │ │ │ ├── alpha_plugin_model.rb │ │ │ │ │ ├── app_and_plugin_model.rb │ │ │ │ │ └── shared_plugin_model.rb │ │ │ │ └── views/ │ │ │ │ ├── alpha_plugin/ │ │ │ │ │ └── a_view.html.erb │ │ │ │ ├── app_and_plugin/ │ │ │ │ │ └── a_view.html.erb │ │ │ │ ├── layouts/ │ │ │ │ │ └── plugin_layout.erb │ │ │ │ ├── namespace/ │ │ │ │ │ ├── alpha_plugin/ │ │ │ │ │ │ └── a_view.html.erb │ │ │ │ │ ├── app_and_plugin/ │ │ │ │ │ │ └── a_view.html.erb │ │ │ │ │ └── shared_plugin/ │ │ │ │ │ └── a_view.html.erb │ │ │ │ └── shared_plugin/ │ │ │ │ └── a_view.html.erb │ │ │ ├── lib/ │ │ │ │ ├── alpha_plugin_lib_model.rb │ │ │ │ └── app_and_plugin_lib_model.rb │ │ │ └── locales/ │ │ │ └── en.yml │ │ ├── beta_plugin/ │ │ │ ├── app/ │ │ │ │ ├── controllers/ │ │ │ │ │ ├── app_and_plugin_controller.rb │ │ │ │ │ ├── namespace/ │ │ │ │ │ │ └── shared_plugin_controller.rb │ │ │ │ │ └── shared_plugin_controller.rb │ │ │ │ ├── models/ │ │ │ │ │ └── shared_plugin_model.rb │ │ │ │ └── views/ │ │ │ │ ├── namespace/ │ │ │ │ │ └── shared_plugin/ │ │ │ │ │ └── a_view.html.erb │ │ │ │ └── shared_plugin/ │ │ │ │ └── a_view.html.erb │ │ │ ├── init.rb │ │ │ └── locales/ │ │ │ └── en.yml │ │ ├── not_a_plugin/ │ │ │ └── public/ │ │ │ └── should_not_be_copied.txt │ │ ├── test_assets/ │ │ │ ├── app/ │ │ │ │ ├── controllers/ │ │ │ │ │ └── assets_controller.rb │ │ │ │ └── views/ │ │ │ │ ├── assets/ │ │ │ │ │ └── index.html.erb │ │ │ │ └── layouts/ │ │ │ │ └── assets.html.erb │ │ │ ├── init.rb │ │ │ └── public/ │ │ │ ├── file.txt │ │ │ └── subfolder/ │ │ │ └── file_in_subfolder.txt │ │ ├── test_assets_with_assets_directory/ │ │ │ ├── assets/ │ │ │ │ ├── file.txt │ │ │ │ └── subfolder/ │ │ │ │ └── file_in_subfolder.txt │ │ │ └── init.rb │ │ ├── test_assets_with_no_subdirectory/ │ │ │ ├── assets/ │ │ │ │ └── file.txt │ │ │ └── init.rb │ │ ├── test_code_mixing/ │ │ │ ├── app/ │ │ │ │ └── things/ │ │ │ │ └── thing.rb │ │ │ └── init.rb │ │ ├── test_load_path/ │ │ │ └── init.rb │ │ ├── test_migration/ │ │ │ ├── db/ │ │ │ │ └── migrate/ │ │ │ │ ├── 001_create_tests.rb │ │ │ │ ├── 002_create_others.rb │ │ │ │ └── 003_create_extras.rb │ │ │ └── init.rb │ │ ├── test_plugin_mailing/ │ │ │ ├── app/ │ │ │ │ ├── models/ │ │ │ │ │ └── plugin_mail.rb │ │ │ │ └── views/ │ │ │ │ └── plugin_mail/ │ │ │ │ ├── mail_from_plugin.erb │ │ │ │ ├── multipart_from_plugin_html.html.erb │ │ │ │ ├── multipart_from_plugin_plain.html.erb │ │ │ │ ├── multipart_from_plugin_with_application_template_html.html.erb │ │ │ │ └── multipart_from_plugin_with_application_template_plain.html.erb │ │ │ └── init.rb │ │ ├── test_routing/ │ │ │ ├── app/ │ │ │ │ └── controllers/ │ │ │ │ ├── namespace/ │ │ │ │ │ └── test_routing_controller.rb │ │ │ │ └── test_routing_controller.rb │ │ │ ├── config/ │ │ │ │ └── routes.rb │ │ │ └── init.rb │ │ └── test_testing/ │ │ ├── app/ │ │ │ └── README.txt │ │ ├── init.rb │ │ └── test/ │ │ ├── fixtures/ │ │ │ └── testing_fixtures.yml │ │ └── unit/ │ │ └── override_test.rb │ └── unit/ │ ├── action_mailer_test.rb │ ├── arbitrary_code_mixing_test.rb │ ├── assets_test.rb │ ├── backwards_compat_test.rb │ ├── load_path_test.rb │ ├── migration_test.rb │ ├── model_and_lib_test.rb │ ├── plugins_test.rb │ ├── test_testing/ │ │ └── override_test.rb │ └── testing_test.rb ├── exceptional/ │ ├── History.txt │ ├── Manifest │ ├── README │ ├── Rakefile │ ├── bin/ │ │ └── ginger │ ├── exceptional.gemspec │ ├── exceptional.yml │ ├── ginger_scenarios.rb │ ├── init.rb │ ├── install.rb │ ├── lib/ │ │ ├── exceptional/ │ │ │ ├── api.rb │ │ │ ├── bootstrap.rb │ │ │ ├── config.rb │ │ │ ├── exception_data.rb │ │ │ ├── integration/ │ │ │ │ └── rails.rb │ │ │ ├── log.rb │ │ │ ├── remote.rb │ │ │ └── version.rb │ │ └── exceptional.rb │ └── spec/ │ ├── api_spec.rb │ ├── bootstrap_spec.rb │ ├── config_spec.rb │ ├── exception_data_spec.rb │ ├── exceptional_rescue_from_spec.rb │ ├── exceptional_spec.rb │ ├── log_spec.rb │ ├── remote_spec.rb │ └── spec_helper.rb ├── googlecharts/ │ ├── History.txt │ ├── License.txt │ ├── Manifest.txt │ ├── README │ ├── README.markdown │ ├── README.txt │ ├── Rakefile │ ├── VERSION │ ├── config/ │ │ ├── hoe.rb │ │ └── requirements.rb │ ├── lib/ │ │ ├── gchart/ │ │ │ ├── aliases.rb │ │ │ ├── tasks/ │ │ │ │ ├── deployment.rake │ │ │ │ ├── environment.rake │ │ │ │ ├── rspec.rake │ │ │ │ └── website.rake │ │ │ ├── theme.rb │ │ │ └── version.rb │ │ ├── gchart.rb │ │ ├── googlecharts.rb │ │ └── themes.yml │ ├── script/ │ │ ├── destroy │ │ ├── generate │ │ └── txt2html │ ├── setup.rb │ ├── spec/ │ │ ├── fixtures/ │ │ │ ├── another_test_theme.yml │ │ │ └── test_theme.yml │ │ ├── gchart_spec.rb │ │ ├── spec_helper.rb │ │ └── theme_spec.rb │ └── website/ │ ├── index.html │ ├── index.txt │ ├── javascripts/ │ │ └── rounded_corners_lite.inc.js │ ├── stylesheets/ │ │ └── screen.css │ └── template.rhtml ├── gravatar/ │ ├── .gitignore │ ├── MIT-LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── about.yml │ ├── init.rb │ ├── lib/ │ │ └── gravatar.rb │ └── spec/ │ └── gravatar_spec.rb ├── jrails/ │ ├── CHANGELOG │ ├── LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── VERSION.yml │ ├── bin/ │ │ └── jrails │ ├── init.rb │ ├── install.rb │ ├── javascripts/ │ │ ├── jquery-ui.js │ │ ├── jquery.js │ │ ├── jrails.js │ │ └── sources/ │ │ └── jrails.js │ ├── jrails.gemspec │ ├── lib/ │ │ ├── jquery_selector_assertions.rb │ │ ├── jrails.rb │ │ └── tasks/ │ │ └── jrails.rake │ └── rails/ │ └── init.rb ├── open_id_authentication/ │ ├── CHANGELOG │ ├── README │ ├── Rakefile │ ├── generators/ │ │ ├── open_id_authentication_tables/ │ │ │ ├── open_id_authentication_tables_generator.rb │ │ │ └── templates/ │ │ │ └── migration.rb │ │ └── upgrade_open_id_authentication_tables/ │ │ ├── templates/ │ │ │ └── migration.rb │ │ └── upgrade_open_id_authentication_tables_generator.rb │ ├── init.rb │ ├── lib/ │ │ ├── open_id_authentication/ │ │ │ ├── association.rb │ │ │ ├── db_store.rb │ │ │ ├── mem_cache_store.rb │ │ │ ├── nonce.rb │ │ │ ├── request.rb │ │ │ ├── tasks/ │ │ │ │ └── open_id_authentication_tasks.rake │ │ │ └── timeout_fixes.rb │ │ └── open_id_authentication.rb │ └── test/ │ ├── mem_cache_store_test.rb │ ├── normalize_test.rb │ ├── open_id_authentication_test.rb │ ├── status_test.rb │ └── test_helper.rb ├── pickle/ │ ├── History.txt │ ├── License.txt │ ├── README.rdoc │ ├── Rakefile │ ├── Todo.txt │ ├── VERSION │ ├── features/ │ │ ├── app/ │ │ │ ├── app.rb │ │ │ ├── blueprints.rb │ │ │ ├── factories.rb │ │ │ └── views/ │ │ │ └── notifier/ │ │ │ ├── email.erb │ │ │ └── user_email.erb │ │ ├── email/ │ │ │ └── email.feature │ │ ├── generator/ │ │ │ └── generators.feature │ │ ├── path/ │ │ │ ├── models_page.feature │ │ │ └── named_route_page.feature │ │ ├── pickle/ │ │ │ ├── create_from_active_record.feature │ │ │ ├── create_from_factory_girl.feature │ │ │ └── create_from_machinist.feature │ │ ├── step_definitions/ │ │ │ ├── email_steps.rb │ │ │ ├── extra_email_steps.rb │ │ │ ├── fork_steps.rb │ │ │ ├── generator_steps.rb │ │ │ ├── path_steps.rb │ │ │ └── pickle_steps.rb │ │ └── support/ │ │ ├── env.rb │ │ └── paths.rb │ ├── garlic.rb │ ├── init.rb │ ├── lib/ │ │ ├── pickle/ │ │ │ ├── adapter.rb │ │ │ ├── config.rb │ │ │ ├── email/ │ │ │ │ ├── parser.rb │ │ │ │ └── world.rb │ │ │ ├── email.rb │ │ │ ├── parser/ │ │ │ │ └── matchers.rb │ │ │ ├── parser.rb │ │ │ ├── path/ │ │ │ │ └── world.rb │ │ │ ├── path.rb │ │ │ ├── session/ │ │ │ │ └── parser.rb │ │ │ ├── session.rb │ │ │ ├── version.rb │ │ │ └── world.rb │ │ └── pickle.rb │ ├── pickle.gemspec │ ├── rails_generators/ │ │ └── pickle/ │ │ ├── pickle_generator.rb │ │ └── templates/ │ │ ├── email_steps.rb │ │ ├── env.rb │ │ ├── paths.rb │ │ └── pickle_steps.rb │ └── spec/ │ ├── lib/ │ │ ├── pickle_adapter_spec.rb │ │ ├── pickle_config_spec.rb │ │ ├── pickle_email_parser_spec.rb │ │ ├── pickle_email_spec.rb │ │ ├── pickle_parser_matchers_spec.rb │ │ ├── pickle_parser_spec.rb │ │ ├── pickle_path_spec.rb │ │ ├── pickle_session_spec.rb │ │ └── pickle_spec.rb │ └── spec_helper.rb ├── prepend_engine_views/ │ └── init.rb ├── redmine_s3/ │ ├── README.rdoc │ ├── config/ │ │ ├── locales/ │ │ │ └── en.yml │ │ └── s3.yml.example │ ├── init.rb │ ├── lang/ │ │ └── en.yml │ ├── lib/ │ │ ├── S3.rb │ │ ├── redmine_s3/ │ │ │ └── connection.rb │ │ └── s3_helper.rb │ └── test/ │ └── test_helper.rb ├── rfpdf/ │ ├── CHANGELOG │ ├── MIT-LICENSE │ ├── README │ ├── init.rb │ ├── lib/ │ │ ├── rfpdf/ │ │ │ ├── bookmark.rb │ │ │ ├── chinese.rb │ │ │ ├── errors.rb │ │ │ ├── fpdf.rb │ │ │ ├── fpdf_eps.rb │ │ │ ├── japanese.rb │ │ │ ├── korean.rb │ │ │ ├── makefont.rb │ │ │ ├── rfpdf.rb │ │ │ └── view.rb │ │ └── rfpdf.rb │ └── test/ │ └── test_helper.rb ├── ruby-net-ldap-0.0.4/ │ ├── COPYING │ ├── ChangeLog │ ├── LICENCE │ ├── README │ ├── lib/ │ │ └── net/ │ │ ├── ber.rb │ │ ├── ldap/ │ │ │ ├── dataset.rb │ │ │ ├── entry.rb │ │ │ ├── filter.rb │ │ │ ├── pdu.rb │ │ │ └── psw.rb │ │ ├── ldap.rb │ │ └── ldif.rb │ └── tests/ │ ├── testber.rb │ ├── testdata.ldif │ ├── testem.rb │ ├── testfilter.rb │ ├── testldap.rb │ ├── testldif.rb │ └── testpsw.rb ├── seed_dump/ │ ├── CHANGELOG.rdoc │ ├── MIT-LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── VERSION │ ├── lib/ │ │ ├── seed_dump/ │ │ │ └── railtie.rb │ │ ├── seed_dump.rb │ │ └── tasks/ │ │ └── seed_dump.rake │ ├── seed_dump.gemspec │ └── test/ │ ├── seed_dump_test.rb │ └── test_helper.rb ├── selenium-on-rails/ │ ├── CHANGELOG │ ├── LICENSE-2.0.txt │ ├── README.md │ ├── Rakefile │ ├── doc/ │ │ ├── classes/ │ │ │ ├── SeleniumController.html │ │ │ ├── SeleniumHelper.html │ │ │ ├── SeleniumOnRails/ │ │ │ │ ├── FixtureLoader.html │ │ │ │ ├── PartialsSupport.html │ │ │ │ ├── Paths.html │ │ │ │ ├── RSelenese.html │ │ │ │ ├── Renderer.html │ │ │ │ ├── Selenese.html │ │ │ │ ├── SuiteRenderer.html │ │ │ │ ├── TestBuilder.html │ │ │ │ ├── TestBuilderAccessors.html │ │ │ │ ├── TestBuilderActions.html │ │ │ │ ├── TestBuilderUserAccessors.html │ │ │ │ └── TestBuilderUserActions.html │ │ │ ├── SeleniumOnRails.html │ │ │ └── SeleniumOnRailsConfig.html │ │ ├── files/ │ │ │ ├── CHANGELOG.html │ │ │ ├── README.html │ │ │ └── lib/ │ │ │ ├── controllers/ │ │ │ │ └── selenium_controller_rb.html │ │ │ ├── selenium_helper_rb.html │ │ │ ├── selenium_on_rails/ │ │ │ │ ├── acceptance_test_runner_rb.html │ │ │ │ ├── fixture_loader_rb.html │ │ │ │ ├── partials_support_rb.html │ │ │ │ ├── paths_rb.html │ │ │ │ ├── renderer_rb.html │ │ │ │ ├── rselenese_rb.html │ │ │ │ ├── selenese_rb.html │ │ │ │ ├── suite_renderer_rb.html │ │ │ │ ├── test_builder_accessors_rb.html │ │ │ │ ├── test_builder_actions_rb.html │ │ │ │ └── test_builder_rb.html │ │ │ ├── selenium_on_rails_config_rb.html │ │ │ └── selenium_on_rails_rb.html │ │ ├── fr_class_index.html │ │ ├── fr_file_index.html │ │ ├── fr_method_index.html │ │ ├── index.html │ │ └── rdoc-style.css │ ├── generators/ │ │ └── selenium/ │ │ ├── USAGE │ │ ├── selenium_generator.rb │ │ └── templates/ │ │ ├── rhtml.html.erb │ │ ├── rselenese.html.erb │ │ └── selenese.html.erb │ ├── init.rb │ ├── lib/ │ │ ├── controllers/ │ │ │ ├── selenium_controller.rb │ │ │ └── switch_environment_controller.rb │ │ ├── selenium_helper.rb │ │ ├── selenium_on_rails/ │ │ │ ├── acceptance_test_runner.rb │ │ │ ├── fixture_loader.rb │ │ │ ├── partials_support.rb │ │ │ ├── paths.rb │ │ │ ├── renderer.rb │ │ │ ├── rselenese.rb │ │ │ ├── selenese.rb │ │ │ ├── suite_renderer.rb │ │ │ ├── tasks/ │ │ │ │ └── test_acceptance.rake │ │ │ ├── test_builder.rb │ │ │ ├── test_builder_accessors.rb │ │ │ ├── test_builder_actions.rb │ │ │ ├── test_builder_user_accessors.rb.example │ │ │ └── test_builder_user_actions.rb.example │ │ ├── selenium_on_rails.rb │ │ ├── selenium_on_rails_config.rb │ │ └── views/ │ │ ├── layouts/ │ │ │ └── layout.html.erb │ │ ├── record.html.erb │ │ ├── selenium_helper.rb │ │ ├── setup.html.erb │ │ └── test_suite.html.erb │ ├── log/ │ │ └── default.yml │ ├── routes.rb │ ├── selenium-core/ │ │ ├── Blank.html │ │ ├── InjectedRemoteRunner.html │ │ ├── RemoteRunner.html │ │ ├── SeleniumLog.html │ │ ├── TestPrompt.html │ │ ├── TestRunner-splash.html │ │ ├── TestRunner.hta │ │ ├── TestRunner.html │ │ ├── domviewer/ │ │ │ ├── domviewer.css │ │ │ ├── domviewer.html │ │ │ └── selenium-domviewer.js │ │ ├── iedoc-core.xml │ │ ├── iedoc.xml │ │ ├── lib/ │ │ │ ├── cssQuery/ │ │ │ │ ├── cssQuery-p.js │ │ │ │ └── src/ │ │ │ │ ├── cssQuery-level2.js │ │ │ │ ├── cssQuery-level3.js │ │ │ │ ├── cssQuery-standard.js │ │ │ │ └── cssQuery.js │ │ │ ├── prototype.js │ │ │ ├── scriptaculous/ │ │ │ │ ├── builder.js │ │ │ │ ├── controls.js │ │ │ │ ├── dragdrop.js │ │ │ │ ├── effects.js │ │ │ │ ├── scriptaculous.js │ │ │ │ ├── slider.js │ │ │ │ └── unittest.js │ │ │ └── snapsie.js │ │ ├── scripts/ │ │ │ ├── find_matching_child.js │ │ │ ├── htmlutils.js │ │ │ ├── injection.html │ │ │ ├── selenium-api.js │ │ │ ├── selenium-browserbot.js │ │ │ ├── selenium-browserdetect.js │ │ │ ├── selenium-commandhandlers.js │ │ │ ├── selenium-executionloop.js │ │ │ ├── selenium-logging.js │ │ │ ├── selenium-remoterunner.js │ │ │ ├── selenium-testrunner.js │ │ │ ├── selenium-version.js │ │ │ ├── ui-doc.html │ │ │ ├── ui-element.js │ │ │ ├── ui-map-sample.js │ │ │ ├── user-extensions.js │ │ │ ├── user-extensions.js.sample │ │ │ └── xmlextras.js │ │ ├── selenium-test.css │ │ ├── selenium.css │ │ └── xpath/ │ │ ├── dom.js │ │ ├── javascript-xpath-0.1.11.js │ │ ├── util.js │ │ ├── xmltoken.js │ │ └── xpath.js │ ├── selenium.yml │ ├── test/ │ │ ├── fixtures/ │ │ │ ├── config.yml │ │ │ └── selenium.yml │ │ ├── paths_test.rb │ │ ├── renderer_test.rb │ │ ├── rselenese_test.rb │ │ ├── selenese_test.rb │ │ ├── selenium_controller_test.rb │ │ ├── selenium_on_rails_config_test.rb │ │ ├── selenium_support_test.rb │ │ ├── setup_test.rb │ │ ├── suite_renderer_test.rb │ │ ├── switch_environment_controller_test.rb │ │ ├── test_builder_functions_authortest.rb │ │ └── test_helper.rb │ └── test_data/ │ ├── _partial.rsel │ ├── backup.html~ │ ├── own_layout.html │ ├── partials/ │ │ ├── _html.html │ │ ├── _nesting.rsel │ │ ├── _rhtml.html.erb │ │ ├── _rsel.rsel │ │ ├── _sel.sel │ │ └── all_partials.rsel │ ├── rhtml.html.erb │ ├── rselenese.rsel │ ├── selenese.sel │ ├── suite_one/ │ │ ├── subsuite/ │ │ │ └── suite_one_subsuite_testcase.sel │ │ ├── suite_one_testcase1.sel │ │ └── suite_one_testcase2.sel │ └── suite_two/ │ └── suite_two_testcase.sel ├── simple-private-messages/ │ ├── MIT-LICENSE │ ├── README.rdoc │ ├── Rakefile │ ├── generators/ │ │ ├── private_message_model/ │ │ │ ├── USAGE │ │ │ ├── private_message_model_generator.rb │ │ │ └── templates/ │ │ │ ├── migration.rb │ │ │ └── model.rb │ │ └── private_message_scaffold/ │ │ ├── USAGE │ │ ├── private_message_scaffold_generator.rb │ │ └── templates/ │ │ ├── controller.rb │ │ ├── view_index.html.erb │ │ ├── view_index_inbox.html.erb │ │ ├── view_index_sent.html.erb │ │ ├── view_new.html.erb │ │ └── view_show.html.erb │ ├── init.rb │ ├── lib/ │ │ └── professionalnerd/ │ │ └── simple_private_messages/ │ │ ├── has_private_messages_extensions.rb │ │ ├── private_message_extensions.rb │ │ └── tasks/ │ │ └── simple_private_messages_tasks.rake │ └── test/ │ ├── database.yml │ ├── fixtures/ │ │ ├── message.rb │ │ └── user.rb │ ├── schema.rb │ ├── test_helper.rb │ └── unit/ │ ├── message_model.rb │ └── user_model.rb ├── textile-editor-helper/ │ ├── CHANGELOG │ ├── MIT-LICENSE │ ├── README │ ├── Rakefile │ ├── assets/ │ │ ├── javascripts/ │ │ │ ├── textile-editor-config.js │ │ │ └── textile-editor.js │ │ └── stylesheets/ │ │ └── textile-editor.css │ ├── init.rb │ ├── install.rb │ ├── lib/ │ │ ├── tasks/ │ │ │ └── textile_editor_helper_tasks.rake │ │ └── textile_editor_helper.rb │ └── test/ │ ├── abstract_unit.rb │ └── textile_editor_helper_test.rb └── yaml_db/ ├── README ├── Rakefile ├── about.yml ├── init.rb ├── lib/ │ ├── tasks/ │ │ └── yaml_db_tasks.rake │ └── yaml_db.rb └── spec/ ├── base.rb ├── yaml_dump_spec.rb ├── yaml_load_spec.rb └── yaml_utils_spec.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /db/*.sqlite3 /db/pgdump_full.sql /log/*.log /tmp/**/* /config/additional_environment.rb /config/database.yml /config/email.yml /config/initializers/session_store.rb /coverage /db/*.db /db/*.sqlite3 !/db/schema.rb /files/* /log/*.log* /log/mongrel_debug /public/dispatch.* /public/plugin_assets /tmp/* /tmp/cache/* /tmp/sessions/* /tmp/sockets/* /tmp/test/* /vendor/rails /config/database.yml /nbproject redmine.tmproj serverlog .DS_Store *.swp *.swo .redcar/ .bundle /bin ================================================ FILE: .gitmodules ================================================ ================================================ FILE: .rspec ================================================ --colour --format profile --loadby mtime --reverse ================================================ FILE: .rvmrc ================================================ #!/usr/bin/env bash # This is an RVM Project .rvmrc file, used to automatically load the ruby # development environment upon cd'ing into the directory # First we specify our desired [@], the @gemset name is optional, # Only full ruby name is supported here, for short names use: # echo "rvm use 1.8.7" > .rvmrc environment_id="ruby-1.8.7-p370@bettermeans" # Uncomment the following lines if you want to verify rvm version per project # rvmrc_rvm_version="1.14.1 (stable)" # 1.10.1 seams as a safe start # eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || { # echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading." # return 1 # } # First we attempt to load the desired environment directly from the environment # file. This is very fast and efficient compared to running through the entire # CLI and selector. If you want feedback on which environment was used then # insert the word 'use' after --create as this triggers verbose mode. if [[ -d "${rvm_path:-$HOME/.rvm}/environments" && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] then \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] && \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true if [[ $- == *i* ]] # check for interactive shells then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells fi else # If the environment file has not yet been created, use the RVM CLI to select. rvm --create use "$environment_id" || { echo "Failed to create RVM environment '${environment_id}'." return 1 } fi # If you use bundler, this might be useful to you: # if [[ -s Gemfile ]] && { # ! builtin command -v bundle >/dev/null || # builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null # } # then # printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n" # gem install bundler # fi # if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null # then # bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete' # fi ================================================ FILE: Gemfile ================================================ source :rubygems gem 'rake', '0.8.7' gem 'rails', '2.3.14' gem 'ruby-debug', '0.10.4' gem 'rubytree', '0.7.0' gem 'rpx_now', '0.6.24' gem 'recurly', '0.3.3' gem 'fleximage', '1.0.4' gem 'reportable', '1.1.2' gem 'comma', :git => 'https://github.com/crafterm/comma.git', :ref => 'rails2' gem 'fastercsv', '1.5.4' gem 'SystemTimer', '1.2.2', :require => 'system_timer', :platforms => :ruby_18 gem 'rack-timeout', '0.0.1' gem 'will_paginate', '2.3.15' gem 'grosser-ssl_requirement', :require => 'ssl_requirement' group :test do gem 'shoulda' gem 'webrat', '0.7.3' gem 'rspec', '1.3.2' gem 'rspec-rails', '1.3.4' gem 'pickle', "0.3.4" gem 'factory_girl', "1.3.3" gem 'object_daddy', "0.4.3" #gem 'capybara-webkit' gem 'fakeweb' end group :development do gem 'sqlite3-ruby', :require => 'sqlite3' gem 'pg' gem 'mysql2', '< 0.3' gem 'guard-rspec' gem 'spork', '0.8.5' gem 'guard-spork' #gem 'reek' gem 'ruby2ruby', '1.2.2' gem 'heckle' end group :development, :test do gem 'capybara' gem 'steak' end ================================================ FILE: Guardfile ================================================ # A sample Guardfile # More info at https://github.com/guard/guard#readme guard 'spork', :rspec_env => { 'RAILS_ENV' => 'test' } do watch('config/application.rb') watch('config/environment.rb') watch('config/environments/test.rb') watch(%r{^config/initializers/.+\.rb$}) watch('Gemfile') watch('Gemfile.lock') watch('spec/spec_helper.rb') { :rspec } watch('spec/factories.rb') watch('db/seeds.rb') watch(%r{features/support/}) { :cucumber } watch(%r{^app/models/.+\.rb$}) watch(%r{^app/controllers/.+\.rb$}) end guard 'rspec', :version => 1, :cli => '--drb --color' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } # Rails example watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } watch(%r{^spec/support/(.+)\.rb$}) { "spec" } watch('config/routes.rb') { "spec/routing" } watch('app/controllers/application_controller.rb') { "spec/controllers" } # Capybara request specs watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } # Turnip features and steps watch(%r{^spec/acceptance/(.+)\.feature$}) watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } end ================================================ FILE: README.md ================================================ BetterMeans ----------- BetterMeans is giving birth to a new kind of company. An Open Enterprise. More details can be found at http://bettermeans.com and here http://bettermeans.org Getting started --------------- * `git clone git@github.com:Bettermeans/bettermeans.git` * bundle install * Rename `database.yml.example` to `database.yml` * Run rake `db:create:all` and `rake db:schema:load` That's it. Now you're ready to change the world. Here's to making a dent in things together! Dev notes --------- Platform workstream: http://bettermeans.com/projects/2/dashboard IRC: #bettermeans irc.feenode.net mailinglist: bettermeans@librelist.org (or build in workstream forum) Testing ------- capybara-webkit depends on a WebKit implementation from Qt as explained in https://github.com/thoughtbot/capybara-webkit/wiki/Installing-QT Translating ----------- You can find language specific translation groups at: https://www.transifex.net/projects/p/bettermeans Known issues ------------ Attachments doesn't work in dev environment Logging in via the janrain plugin (e.g. google, twitter...etc) won't work in dev environment (if you need to work with this, drop me a message, there's an involved workaround) License and legalese -------------------- Our codebase is based largely on an early fork of Redmine. Redmine is open source and released under the terms of the GNU General Public License v2 (GPL). All redmine code is Copyright (C) 2006-2011 Jean-Philippe Lang All non-redmine code is Copyright (C) Shereef Bishay, and is dual-licensed: you may use either the GNU General Public License v2 (GPL), or the MIT License (see http://www.opensource.org/licenses/mit-license.php). Thanks for joining us! May our work be used for the greater good of everyone. ================================================ FILE: Rakefile ================================================ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. require(File.join(File.dirname(__FILE__), 'config', 'boot')) require 'rake' require 'rake/testtask' require 'rake/rdoctask' require 'tasks/rails' ================================================ FILE: app/controllers/account_controller.rb ================================================ # TODO: rename this to something like SessionsController after integration specs are done class AccountController < ApplicationController # RPX does not pass Rails form tokens... skip_before_filter :verify_authenticity_token, :only => [ :rpx_token, :register ] # prevents login action from being filtered by check_if_login_required application scope filter skip_before_filter :check_if_login_required, :except => [ :cancel ] ssl_required :all # Login request and validation def login set_invitation_token if request.get? logout_user session[:invitation_token] = @invitation_token render :layout => 'static' elsif open_id_authenticate? open_id_authenticate(params[:openid_url]) else password_authentication(@invitation_token) end end def rpx_token find_user_by_identifier || find_user_by_mail || create_new_user message = reactivate_user successful_authentication(@user, @invitation_token, message) end # Log out current user and redirect to welcome page def logout cookies.delete :autologin current_user.delete_autologin_tokens logout_user redirect_to home_url end # Enable user to choose a new password def lost_password redirect_to(home_url) && return unless Setting.lost_password? validate_token || create_token end # User self-registration def register redirect_to(home_url) && return unless check_registration pick_plan if request.get? logout_and_invite else initialize_user_with_plan return if register_user_with_auth_source || register_user end render :layout => 'static' end # Token based account activation def activate redirect_to(home_url) && return unless can_activate? && user = registered_user user.activate if user.save @token.destroy flash.now[:success] = l(:notice_account_activated) successful_authentication(user) else render :action => 'login', :layout => 'static' end end def cancel current_user.cancel_account! render_message(l(:notice_account_canceled)) end private def password_authentication(invitation_token=nil) user = User.try_to_login(params[:username], params[:password]) if user.nil? invalid_credentials elsif user.new_record? onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) elsif user.active? successful_authentication(user,invitation_token) else inactive_user end end def open_id_authenticate(openid_url) authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration| if result.successful? user = User.find_or_initialize_by_identity_url(identity_url) if user.new_record? # Self-registration off redirect_to(home_url) && return unless Setting.self_registration? # Create on the fly user.login = registration['nickname'] unless registration['nickname'].nil? user.mail = registration['email'] unless registration['email'].nil? user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil? user.random_password user.status = User::STATUS_REGISTERED case Setting.self_registration when '1' register_by_email_activation(user) do onthefly_creation_failed(user) end when '3' register_automatically(user) do onthefly_creation_failed(user) end else register_manually_by_administrator(user) do onthefly_creation_failed(user) end end else # Existing record if user.active? successful_authentication(user) else account_pending end end end end end def successful_authentication(user, invitation_token = nil, msg=nil) # Valid user self.logged_user = user Track.log(Track::LOGIN,session[:client_ip]) if invitation_token invitation = Invitation.find_by_token(invitation_token) invitation.accept(user) if invitation end # generate a key and set cookie if autologin if params[:autologin] && Setting.autologin? token = Token.create(:user => user, :action => 'autologin') cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now } end if msg render_message(msg) else redirect_back_or_default :controller => 'welcome', :action => 'index' end end # Onthefly creation failed, display the registration form to fill/fix attributes def onthefly_creation_failed(user, auth_source_options = { }) @user = user session[:auth_source_registration] = auth_source_options unless auth_source_options.empty? render :action => 'register' end def invalid_credentials # BUGBUG: "invalid_credentials", spelling flash.now[:error] = l(:notice_account_invalid_creditentials) render :layout => 'static' end def inactive_user flash.now[:error] = l(:notice_account_inactive_user) render_error(l(:notice_account_inactive_user)) end # Register a user for email activation. # # Pass a block for behavior when a user fails to save def register_by_email_activation(user, invitation_token = nil) # BUGBUG: this should just be `invitation_token.blank?` # when it's nil this throws an error unless invitation_token.empty? || invitation_token.nil? invitation = Invitation.find_by_token invitation_token invitation.new_mail = user.mail invitation.save end if invitation && invitation.mail == user.mail return register_automatically(user) end token = Token.new(:user => user, :action => "register") if user.save and token.save Mailer.send_later(:deliver_register,token) flash.now[:success] = l(:notice_account_register_done) render :action => 'login', :layout => 'static' return true else yield if block_given? return false end end # Automatically register a user # # Pass a block for behavior when a user fails to save def register_automatically(user, &block) # Automatic activation user.status = User::STATUS_ACTIVE user.last_login_on = Time.now if user.save self.logged_user = user Track.log(Track::LOGIN,session[:client_ip]) redirect_with_flash :success, l(:notice_account_activated), :controller => 'welcome', :action => 'index' return true else yield if block_given? return false end end # Manual activation by the administrator # # Pass a block for behavior when a user fails to save def register_manually_by_administrator(user, &block) if user.save # Sends an email to the administrators Mailer.send_later(:deliver_account_activation_request,user) account_pending else yield if block_given? end end def account_pending flash.now[:notice] = l(:notice_account_pending) render :action => 'login', :layout => 'static' end def random_email "#{(0...8).map{65.+(rand(25)).chr}.join}_noemail@bettermeans.com" end def data @data ||= RPXNow.user_data(params[:token]) raise "hackers?" unless @data @data end def invitation return @invitation if defined? @invitation if session[:invitation_token] @invitation = Invitation.find_by_token(session[:invitation_token]) @invitation_token = session[:invitation_token] end @invitation end def invitation_mail invitation.mail if invitation end def update_invitation invitation.update_attributes(:new_mail => @user.mail) if invitation end def find_user_by_identifier if @user = User.find_by_identifier(data[:identifier]) update_invitation true end end def find_user_by_mail if data[:email] && @user = User.find_by_mail(data[:email]) @user.update_attributes(:identifier => data[:identifier]) true end end def create_new_user @user = User.new(:firstname => name, :mail => mail, :identifier => data[:identifier]) @user.login = available_login update_invitation save_user_or_raise end def reactivate_user unless @user.active? @user.reactivate return l(:notice_account_reactivated) end end def save_user_or_raise unless @user.save session[:debug_user] = @user.inspect session[:debug_data] = data.inspect raise "Couldn't create new account" end end def name data[:name] || data[:username] end def mail # twitter accounts don't give email so we generate a random one # TODO: get a real email from the user, or don't require one data[:email] || invitation_mail || random_email end def available_login # BUGBUG: if data[:email] is nil this won't fail based on validations # should probably use mail from up above User.find_available_login([data[:username], name]) || data[:email] end def logout_user self.logged_user = nil end def set_invitation_token session[:invitation_token] = params[:invitation_token] || session[:invitation_token] @invitation_token = session[:invitation_token] end def open_id_authenticate? Setting.openid? && using_open_id? end def update_password # BUGBUG: password confirmation isn't validated when nil @user.password = params[:new_password] @user.password_confirmation = params[:new_password_confirmation] if @user.save @token.destroy flash.now[:success] = l(:notice_account_password_updated) render :action => 'login', :layout => 'static' true end end def valid_user user = User.find_by_mail(params[:mail]) if user.nil? flash.now[:error] = l(:notice_account_unknown_email) nil elsif user.auth_source_id flash.now[:error] = l(:notice_can_t_change_password) nil else user end end def valid_token if token && !token.expired? token else redirect_to(home_url) nil end end def token find_token('recovery') end # TODO: should be able to somehow combine #token and #find_token def find_token(action) @token ||= if params[:token] Token.find_by_action_and_value(action, params[:token]) end end def send_mail(token) Mailer.send_later(:deliver_lost_password, token) flash.now[:success] = l(:notice_account_lost_email_sent) render :action => 'login', :layout => 'static' end def validate_token if params[:token] && @token = valid_token @user = @token.user return true if request.post? && update_password render :template => "account/password_recovery" true end end def create_token if request.post? && user = valid_user token = Token.new(:user => user, :action => "recovery") token.save && send_mail(token) end end def pick_plan if params[:plan] @plan_id = Plan.find_by_code(params[:plan]).id elsif params[:plan_id] @plan_id = params[:plan_id] else @plan_id = Plan.find_by_code(Plan::FREE_CODE).id end end def register_user @user.login = params[:user][:login] @user.password = params[:password] @user.password_confirmation = params[:password_confirmation] case Setting.self_registration when '1' register_by_email_activation(@user,params[:invitation_token]) when '3' register_automatically(@user) false else register_manually_by_administrator(@user) false end end def invite_to_login session[:invitation_token] = params[:invitation_token] invitation = Invitation.find_by_token(params[:invitation_token]) @user.mail = invitation.mail if invitation flash.now[:notice] = "Sign up below to activate your invitation." << "

" << "Login here if you already have an account." end def initialize_user_with_plan @user = User.new(params[:user]) @user.plan_id = @plan_id # TODO: it shouldn't be possible for @user.plan_id to be nil here @user.trial_expires_on = 30.days.from_now if @user.plan_id && !@user.plan.free? # TODO: admin is attr_protected in the model, so it shouldn't be necessary here @user.admin = false @user.status = User::STATUS_REGISTERED end def register_user_with_auth_source return false unless session[:auth_source_registration] @user.status = User::STATUS_ACTIVE @user.login = session[:auth_source_registration][:login] @user.auth_source_id = session[:auth_source_registration][:auth_source_id] if @user.save # TODO: this isn't necessary, as the session gets cleared in logged_user= session[:auth_source_registration] = nil self.logged_user = @user Track.log(Track::LOGIN,session[:client_ip]) redirect_with_flash :notice, l(:notice_account_activated), :controller => 'my', :action => 'account' true end end def logout_and_invite # TODO: this isn't necessary, as the session gets cleared in logged_user= session[:auth_source_registration] = nil logout_user @user = User.new(:language => Setting.default_language) invite_to_login if params[:invitation_token] end def check_registration Setting.self_registration? || session[:auth_source_registration] end def can_activate? Setting.self_registration? && params[:token] && valid_register_token? end def valid_register_token? find_token('register') @token && !@token.expired? end def registered_user # TODO: this is brittle, depending on @token being set earlier user = @token.user user if user.registered? end end ================================================ FILE: app/controllers/activity_stream_preferences_controller.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # Template to generate the controllers class ActivityStreamPreferencesController < ApplicationController include ActivityStreamPreferencesModule before_filter :require_login, :except => :feed end ================================================ FILE: app/controllers/activity_stream_preferences_module.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # activity_stream_preferences_module.rb provides ActivityStreamPreferencesModule require 'digest/sha1' # ActivityStreamPreferencesModule is included in your generated ActivityStreamPereference Controller and provides functionality for User to configure preferences as to where their activities are displayed module ActivityStreamPreferencesModule def index get_user_id @activity_stream_preferences = ActivityStreamPreference.find(:all, :conditions => { ACTIVITY_STREAM_USER_MODEL_ID => @user_id }) build_activities_hash klass = Object::const_get(ACTIVITY_STREAM_USER_MODEL) @user = klass.find(@user_id) if @user.activity_stream_token.blank? @user.update_attribute(:activity_stream_token, Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )) if @user.id == self.current_user.id self.current_user.reload end end respond_to do |format| format.html # index.html.erb format.xml { render :xml => @activity_stream_preferences } end end def create get_user_id @activity_stream_preferences = ActivityStreamPreference.find(:all, :conditions => { ACTIVITY_STREAM_USER_MODEL_ID => @user_id }) build_activities_hash locations = params[:locations] || [] all_locations = [] ACTIVITY_STREAM_LOCATIONS.each do |location| ACTIVITY_STREAM_ACTIVITIES.each_key do |activity| all_locations << "#{activity.to_s}.#{location[0]}" end end (all_locations - locations).each do |location| activity = @activities[location] if activity @activities.delete(location) next end activity = ActivityStreamPreference.new (activity_name,location_id) = location.split('.') activity.activity = activity_name activity.location = location_id activity.send("#{ACTIVITY_STREAM_USER_MODEL_ID}=", @user_id) activity.save! end @activities.each_value { |a| a.destroy } respond_to do |format| flash.now[:success] = 'Activity Stream Preferences were successfully updated.' format.html do if current_user.admin? && params[ACTIVITY_STREAM_USER_MODEL_ID.to_sym] != current_user.id.to_s redirect_to(activity_stream_preferences_path(ACTIVITY_STREAM_USER_MODEL_ID => @user_id)) else redirect_to(activity_stream_preferences_path) end end format.xml { head :ok } end end protected def get_user_id if User.current.admin? && params[ACTIVITY_STREAM_USER_MODEL_ID.to_sym] @user_id = params[ACTIVITY_STREAM_USER_MODEL_ID.to_sym] else @user_id = User.current.id end end def build_activities_hash @activities = {} @activity_stream_preferences.each do |a| key = "#{a.activity}.#{a.location}" @activities[key] = a end end end ================================================ FILE: app/controllers/activity_streams_controller.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # Template to generate the controllers class ActivityStreamsController < ApplicationController include ActivityStreamsModule before_filter :authorize, :except => [ :index, :feed] ssl_required :all def index respond_to do |wants| wants.js do render :update do |page| if params[:refresh] page.replace "activity_stream_list", :partial => "activity_streams/activity_stream_list", :locals => { :user_id => params[:user_id], :project_id => params[:project_id], :with_subprojects => params[:with_subprojects], :limit => params[:limit], :max_created_at => nil, :show_refresh => true} page.call "arm_fancybox" #attaches fancybox triggers to new issues page.call "break_long_words" else page.replace "activity_stream_bottom", :partial => "activity_streams/activity_stream_list", :locals => { :user_id => params[:user_id], :project_id => params[:project_id], :with_subprojects => params[:with_subprojects], :limit => params[:limit], :max_created_at => params[:max_created_at], :show_refresh => false} page.call "arm_fancybox" #attaches fancybox triggers to new issues page.call "break_long_words" end end end end end end ================================================ FILE: app/controllers/activity_streams_module.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # activity_streams_module.rb provides ActivityStreamsModule # # ActivityStreamsModule is included in your generated ActivityStreams Controller and provides base functionality for the Activity Streams Plug-in module ActivityStreamsModule def index @activity_streams = ActivityStream.find(:all, :limit => 200, :order => "updated_at DESC") respond_to do |format| format.html format.xml { render :xml => @activity_streams } end end def show @activity_stream = ActivityStream.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @activity_stream } end end def new @activity_stream = ActivityStream.new respond_to do |format| format.html format.xml { render :xml => @activity_stream } end end def edit @activity_stream = ActivityStream.find(params[:id]) end def create @activity_stream = ActivityStream.new(params[:activity_stream]) respond_to do |format| if @activity_stream.save flash.now[:success] = 'ActivityStream was successfully created.' format.html { redirect_to(@activity_stream) } format.xml { render :xml => @activity_stream, :status => :created, :location => @activity_stream } else format.html { render :action => "new" } format.xml { render :xml => @activity_stream.errors, :status => :unprocessable_entity } end end end def update @activity_stream = ActivityStream.find(params[:id]) respond_to do |format| if @activity_stream.update_attributes(params[:activity_stream]) flash.now[:success] = 'ActivityStream was successfully updated.' format.html { redirect_to(@activity_stream) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @activity_stream.errors, :status => :unprocessable_entity } end end end def destroy @activity_stream = ActivityStream.find(params[:id]) respond_to do |format| if (current_user.admin? || (current_user.id == @activity_stream.actor_id && @activity_stream.actor_type == ACTIVITY_STREAM_USER_MODEL)) && @activity_stream.soft_destroy flash.now[:success] = 'Activity Removed.' format.html { redirect_to "#{request.protocol}#{request.host_with_port}#{params[:ref]}" } format.xml { head :ok } else flash.now[:error] = 'Unexpected Error removing ActivityStream.' format.html {redirect_to "#{request.protocol}#{request.host_with_port}#{params[:ref]}"} format.xml { head :error } end end end end ================================================ FILE: app/controllers/admin_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class AdminController < ApplicationController layout 'admin' before_filter :require_admin, :except => :user_stats ssl_required :all helper :sort include SortHelper def index @no_configuration_data = Redmine::DefaultData::Loader::no_data? end def projects @status = params[:status] ? params[:status].to_i : 1 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) unless params[:name].blank? name = "%#{params[:name].strip.downcase}%" c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name] end @projects = Project.find :all, :order => 'lft', :conditions => c.conditions render :action => "projects", :layout => false if request.xhr? end def plugins @plugins = Redmine::Plugin.all end # Loads the default configuration # (roles, trackers, statuses, workflow, enumerations) def default_configuration if request.post? begin Redmine::DefaultData::Loader::load(params[:lang]) flash.now[:success] = l(:notice_default_data_loaded) rescue Exception => e flash.now[:error] = l(:error_can_t_load_default_data, e.message) end end redirect_to :action => 'index' end def test_email raise_delivery_errors = ActionMailer::Base.raise_delivery_errors # Force ActionMailer to raise delivery errors so we can catch it ActionMailer::Base.raise_delivery_errors = true begin @test = Mailer.deliver_test(User.current) flash.now[:success] = l(:notice_email_sent, User.current.mail) rescue Exception => e flash.now[:error] = l(:notice_email_error, e.message) end ActionMailer::Base.raise_delivery_errors = raise_delivery_errors redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications' end def user_data_dump @users = User.find(:all, :conditions => {:status => 1}) render :csv => @users end def info @db_adapter_name = ActiveRecord::Base.connection.adapter_name @checklist = [ [:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?], [:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)], [:text_rmagick_available, Object.const_defined?(:Magick)] ] end end ================================================ FILE: app/controllers/application_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'uri' require 'cgi' require 'ruby-debug' class ApplicationController < ActionController::Base include Redmine::I18n include LogActivityStreams before_filter :set_user_ip include SslRequirement # don't require ssl in development skip_before_filter :ensure_proper_protocol if Rails.env.development? layout 'gooey' # Remove broken cookie after upgrade from 0.8.x (#4292) # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 # TODO: remove it when Rails is fixed before_filter :delete_broken_cookies def delete_broken_cookies if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/ cookies.delete '_redmine_session' redirect_to home_path return false end end before_filter :user_setup, :check_if_login_required, :set_localization filter_parameter_logging :password protect_from_forgery rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token include Redmine::Search::Controller include Redmine::MenuManager::MenuController helper Redmine::MenuManager::MenuHelper def set_user_ip session[:client_ip] = request.headers['X-Real-Ip'] unless session[:client_ip] end def user_setup # Check the settings cache for each request Setting.check_cache # Find the current user User.current = find_current_user end def redirect_with_flash(flash_type,msg,*params) flash[flash_type] = msg redirect_to(*params) end def current_user User.current end # Returns the current user or nil if no user is logged in # and starts a session if needed def find_current_user if session[:user_id] # existing session user = (User.active.find(session[:user_id]) rescue nil) user elsif cookies[:autologin] && Setting.autologin? # auto-login feature starts a new session user = User.try_to_autologin(cookies[:autologin]) session[:user_id] = user.id if user Track.log(Track::LOGIN,session[:client_ip]) if user user elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) && accept_key_auth_actions.include?(params[:action]) if params[:key].present? # Use API key User.find_by_api_key(params[:key]) else # HTTP Basic, either username/password or API key/random authenticate_with_http_basic do |username, password| #TODO: track login here: Track.log(Track::LOGIN,session[:client_ip]) User.try_to_login(username, password) || User.find_by_api_key(username) end end end end # Sets the logged in user def logged_user=(user) #resetting session, but keeping client_ip ip = session[:client_ip] if session[:client_ip] reset_session session[:client_ip] = ip if user && user.is_a?(User) User.current = user session[:user_id] = user.id else User.current = User.anonymous end end # check if login is globally required to access the application def check_if_login_required # no check needed if user is already logged in return true if User.current.logged? require_login if Setting.login_required? end def set_localization lang = nil if User.current.logged? lang = find_language(User.current.language) end lang ||= Setting.default_language set_language_if_valid(lang) end def data_admin_logged_in? return true if User.current == User.find_by_login("shereef") || User.current == User.find_by_login("adelegb") || User.current == User.find_by_login("crabari") return false end def require_login if !User.current.logged? # Extract only the basic url parameters on non-GET requests if request.get? url = url_for(params) else url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) end respond_to do |format| format.html { redirect_to :controller => "account", :action => "login", :back_url => url } format.xml { head :unauthorized } format.json { head :unauthorized } end return false end true end def require_admin return unless require_login if !User.current.admin? render_403 return false end true end def deny_access User.current.logged? ? render_403 : require_login end # Authorize the user for the requested action def authorize(ctrl = params[:controller], action = params[:action], global = false) return true if params[:format] == "png" allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global) allowed ? true : deny_access end # Authorize the user for the requested action outside a project def authorize_global(ctrl = params[:controller], action = params[:action], global = true) authorize(ctrl, action, global) end # make sure that the user is a member of the project (or admin) if project is private # used as a before_filter for actions that do not require any particular permission on the project def check_project_privacy if @project && @project.active? if @project.is_public? || User.current.allowed_to_see_project?(@project) || User.current.admin? true else User.current.logged? ? render_403 : require_login end else @project = nil render_404 false end end def redirect_back_or_default(default) back_url = CGI.unescape(params[:back_url].to_s) if !back_url.blank? && !back_url.include?("/home/") && !back_url.include?("/front/") begin uri = URI.parse(back_url) # do not redirect user to another host or to the login or register page if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)}) redirect_to(back_url) return end rescue URI::InvalidURIError # redirect to default end end redirect_to default end def render_403 @project = nil render :template => "common/403", :layout => (request.xhr? ? false : 'gooey'), :status => 403 return false end def render_404 render :template => "common/404", :layout => !request.xhr?, :status => 404 return false end def render_error(msg) flash.now[:error] = msg render :text => '', :layout => !request.xhr?, :status => 500 end def render_message(msg) flash.now[:notice] = msg render :text => '', :layout => !request.xhr? end def invalid_authenticity_token redirect_back_or_default(home_path) end def render_feed(items, options={}) @items = items || [] @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @items = @items.slice(0, Setting.feeds_limit.to_i) @title = options[:title] || Setting.app_title render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' end def self.accept_key_auth(*actions) actions = actions.flatten.map(&:to_s) write_inheritable_attribute('accept_key_auth_actions', actions) end def accept_key_auth_actions self.class.read_inheritable_attribute('accept_key_auth_actions') || [] end def attach_files_for_new_issue(issue,attachment_ids) if attachment_ids Attachment.update_all("container_id = #{issue.id}" , "id in (#{attachment_ids}) and container_id = 0" ) end end # TODO: move to model def attach_files(obj, attachments) attached = [] unsaved = [] if attachments && attachments.is_a?(Hash) attachments.each_value do |attachment| file = attachment['file'] next unless file && file.size > 0 a = Attachment.create(:container => obj, :file => file, :description => attachment['description'].to_s.strip, :author => User.current) a.new_record? ? (unsaved << a) : (attached << a) end if unsaved.any? flash.now[:error] = l(:warning_attachments_not_saved, unsaved.size) end end attached end def attach_temp_files(obj, attachments) attached = [] unsaved = [] logger.info { "attaching temp #{attachments.inspect}" } if attachments && attachments.is_a?(Hash) attachments.each_value do |attachment| logger.info { "atatchment #{attachment}" } file = Tempfile.open(attachment) next unless file && file.size > 0 a = Attachment.create(:container => obj, :file => file, :description => '', :author => User.current) a.new_record? ? (unsaved << a) : (attached << a) end if unsaved.any? flash.now[:error] = l(:warning_attachments_not_saved, unsaved.size) end end attached end #replaces newline characters with more binary-compatible ones def cleanup_newline(text) return text unless text and !text.empty? text.gsub(/\r?\n/, "\r\n") end # Same as Rails' simple_format helper without using paragraphs def simple_format_without_paragraph(text) text.to_s. gsub(/\r\n?/, "\n"). # \r\n and \r -> \n gsub(/\n\n+/, "

"). # 2+ newline -> 2 br gsub(/([^\n]\n)(?=[^\n])/, '\1
') # 1 newline -> br end # Returns the number of objects that should be displayed # on the paginated list def per_page_option per_page = nil if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i) per_page = params[:per_page].to_s.to_i session[:per_page] = per_page elsif session[:per_page] per_page = session[:per_page] else per_page = Setting.per_page_options_array.first || 25 end per_page end # qvalues http header parser # code taken from webrick def parse_qvalues(value) tmp = [] if value parts = value.split(/,\s*/) parts.each {|part| if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part) val = m[1] q = (m[2] or 1).to_f tmp.push([val, q]) end } tmp = tmp.sort_by{|val, q| -q} tmp.collect!{|val, q| val} end return tmp rescue nil end # Returns a string that can be used as filename value in Content-Disposition header def filename_for_content_disposition(name) request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name end #breakpoint def bp debugger if ENV['RAILS_ENV'] == 'development' end end ================================================ FILE: app/controllers/attachments_controller.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # class AttachmentsController < ApplicationController before_filter :find_project, :except => :create before_filter :read_authorize, :except => [:destroy, :create] before_filter :delete_authorize, :only => :destroy ssl_required :all verify :method => :post, :only => :destroy unloadable # Send unloadable so it will not be unloaded in development before_filter :redirect_to_s3, :except => [:destroy, :create] def create logger.info { "params #{params.inspect}" } if params[:file] file = params[:file] logger.info { "file #{file.inspect}" } a = Attachment.create(:container_id => params[:container_id], :container_type => params[:container_type], :file => file, :author => User.current) logger.info { "created attachment #{a.inspect}" } end logger.info {"done with create" } render :json => a.to_json end def redirect_to_s3 if @attachment.container.is_a?(Project) @attachment.increment_download end redirect_to("#{RedmineS3::Connection.uri}/#{@attachment.disk_filename}") end def show if @attachment.is_diff? @diff = File.new(@attachment.diskfile, "rb").read render :action => 'diff' elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte @content = File.new(@attachment.diskfile, "rb").read render :action => 'file' else download end end def download if @attachment.container.is_a?(Project) @attachment.increment_download end # images are sent inline send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename), :type => @attachment.content_type, :disposition => (@attachment.image? ? 'inline' : 'attachment') end def destroy # Make sure association callbacks are called @attachment.container.attachments.delete(@attachment) redirect_to :back rescue ::ActionController::RedirectBackError redirect_to :controller => 'projects', :action => 'show', :id => @project end private def find_project @attachment = Attachment.find(params[:id]) # Show 404 if the filename in the url is wrong raise ActiveRecord::RecordNotFound if params[:filename] && params[:filename] != @attachment.filename @project = @attachment.project rescue ActiveRecord::RecordNotFound render_404 end # Checks that the file exists and is readable def file_readable @attachment.readable? ? true : render_404 end def read_authorize @attachment.visible? ? true : deny_access end def delete_authorize @attachment.deletable? ? true : deny_access end end ================================================ FILE: app/controllers/auth_sources_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class AuthSourcesController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all def index list render :action => 'list' unless request.xhr? end # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list } def list @auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10 render :action => "list", :layout => false if request.xhr? end def new @auth_source = AuthSourceLdap.new end def create @auth_source = AuthSourceLdap.new(params[:auth_source]) if @auth_source.save flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'list' else render :action => 'new' end end def edit @auth_source = AuthSource.find(params[:id]) end def update @auth_source = AuthSource.find(params[:id]) if @auth_source.update_attributes(params[:auth_source]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'list' else render :action => 'edit' end end def test_connection @auth_method = AuthSource.find(params[:id]) begin @auth_method.test_connection flash.now[:success] = l(:notice_successful_connection) rescue => text flash.now[:error] = "Unable to connect (#{text})" end redirect_to :action => 'list' end def destroy @auth_source = AuthSource.find(params[:id]) unless @auth_source.users.find(:first) @auth_source.destroy flash.now[:success] = l(:notice_successful_delete) end redirect_to :action => 'list' end end ================================================ FILE: app/controllers/boards_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class BoardsController < ApplicationController default_search_scope :messages before_filter :find_project, :authorize ssl_required :all helper :messages include MessagesHelper helper :sort include SortHelper helper :watchers include WatchersHelper def index @boards = @project.boards # show the board if there is only one if @boards.size == 1 @board = @boards.first show end end def show respond_to do |format| format.html { sort_init 'updated_at', 'desc' sort_update 'created_at' => "#{Message.table_name}.created_at", 'replies' => "#{Message.table_name}.replies_count", 'updated_at' => "#{Message.table_name}.updated_at" @topic_count = @board.topics.count @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page'] @topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '), :include => [:author, {:last_reply => :author}], :limit => @topic_pages.items_per_page, :offset => @topic_pages.current.offset @message = Message.new render :action => 'show', :layout => !request.xhr? } end end verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index } def new @board = Board.new(params[:board]) @board.project = @project if request.post? && @board.save flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' end end def edit if request.post? && @board.update_attributes(params[:board]) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' end end def destroy @board.destroy redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' end private def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? @board = @project.boards.find(params[:id]) if params[:id] rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/comments_controller.rb ================================================ class CommentsController < ApplicationController unloadable before_filter :find_issue, :only => [:index, :create ] before_filter :find_project, :authorize ssl_required :all log_activity_streams :current_user, :name, :updated, :@issue, :subject, :create, :issues, { :object_description_method => :description, :indirect_object => :@journal, :indirect_object_description_method => :notes, :indirect_object_phrase => '' } def index @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_at DESC", :conditions => "notes!=''") end def create @journal = @issue.init_journal(User.current, params[:comment]) @journal.save! @journal.reload @issue.reload render :json => @issue.to_dashboard end private def find_issue @issue = Issue.find(params[:issue_id]) end def find_project @project = @issue.project end end ================================================ FILE: app/controllers/credit_distributions_controller.rb ================================================ class CreditDistributionsController < ApplicationController before_filter :require_admin ssl_required :all def index @credit_distributions = CreditDistribution.all respond_to do |format| format.html format.xml { render :xml => @credit_distributions } end end def show @credit_distribution = CreditDistribution.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @credit_distribution } end end def new @credit_distribution = CreditDistribution.new respond_to do |format| format.html format.xml { render :xml => @credit_distribution } end end def edit @credit_distribution = CreditDistribution.find(params[:id]) end def create @credit_distribution = CreditDistribution.new(params[:credit_distribution]) respond_to do |format| if @credit_distribution.save flash.now[:success] = 'CreditDistribution was successfully created.' format.html { redirect_to(@credit_distribution) } format.xml { render :xml => @credit_distribution, :status => :created, :location => @credit_distribution } else format.html { render :action => "new" } format.xml { render :xml => @credit_distribution.errors, :status => :unprocessable_entity } end end end def update @credit_distribution = CreditDistribution.find(params[:id]) respond_to do |format| if @credit_distribution.update_attributes(params[:credit_distribution]) flash.now[:success] = 'CreditDistribution was successfully updated.' format.html { redirect_to(@credit_distribution) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @credit_distribution.errors, :status => :unprocessable_entity } end end end def destroy @credit_distribution = CreditDistribution.find(params[:id]) @credit_distribution.destroy respond_to do |format| format.html { redirect_to(credit_distributions_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/credit_transfers_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # class CreditTransfersController < ApplicationController ssl_required :all before_filter :authorize_global, :except => :eligible_recipients def index @credit_transfers = CreditTransfer.find(:all, :conditions => "sender_id = #{User.current.id} or recipient_id = #{User.current.id}", :include => [:sender, :recipient, :project],:order => "created_at DESC") project_id_array = Credit.find(:all,:conditions => {:settled_on => nil, :owner_id => User.current.id}).group_by(&:project_id).collect{|p| p[0]} if project_id_array.empty? else @project_list = Project.find(:all, :conditions => "id IN (#{project_id_array.join(",")})").sort! {|x,y| x.name <=> y.name } if params[:selected_project_id] @selected_project_id = Integer(params[:selected_project_id]) @project = Project.find(@selected_project_id) @total_credits = Credit.round(Credit.sum(:amount, :conditions => {:settled_on => nil, :owner_id => User.current.id, :project_id => @project.id})) @user_list = @project.root.all_members #remove current user from list @user_list.delete_if { |a| a.user_id == User.current.id} end end end def create recipient = User.find(params[:credit_transfer][:recipient_id]) project = Project.find(params[:credit_transfer][:project_id]) total_transferred = Credit.transfer User.current, recipient, project, Float(params[:amount]), params[:note] respond_to do |format| flash.now[:success] = "Successfully transferred #{total_transferred} credits to #{recipient.name}" format.html { redirect_to :action => "index" } flash.keep end rescue Exception => e flash.now[:error] = l(:text_failed_to_transfer) + e.message redirect_to :action => "index" flash.keep end def eligible_recipients @project = Project.find(params[:project_id]) @total_credits = Credit.round(Credit.sum(:amount, :conditions => {:settled_on => nil, :owner_id => User.current.id, :project_id => @project.id})) @user_list = "" @user_list = @project.root.all_members #remove current user from list @user_list.delete_if { |a| a.user_id == User.current.id} render :partial => "eligible_recipients" end end ================================================ FILE: app/controllers/credits_controller.rb ================================================ class CreditsController < ApplicationController before_filter :require_admin, :except => [:disable, :enable] before_filter :find_credit, :only => [:disable, :enable] before_filter :self_authorize, :only => [:disable, :enable] ssl_required :all def index @project = Project.find(params[:project_id]) unless params[:project_id].nil? @credits = @project.credits @active_credits = @credits.find_all{|credit| credit.enabled == true }.group_by{|credit| credit.owner_id} respond_to do |format| format.html format.xml { render :xml => @credits } end end def show @credit = Credit.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @credit } end end def new @credit = Credit.new respond_to do |format| format.html format.xml { render :xml => @credit } end end def edit @credit = Credit.find(params[:id]) end def create @credit = Credit.new(params[:credit]) respond_to do |format| if @credit.save flash.now[:success] = 'Credit was successfully created.' format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.xml { render :xml => @credit, :status => :created, :location => @credit } else format.html { render :action => "new" } format.xml { render :xml => @credit.errors, :status => :unprocessable_entity } end end end def update @credit = Credit.find(params[:id]) respond_to do |format| if @credit.update_attributes(params[:credit]) flash.now[:success] = 'Credit was successfully updated.' format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @credit.errors, :status => :unprocessable_entity } end end end def disable respond_to do |format| if @credit.disable format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.js do update_credit_partials end else format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.js do render :update do |page| page.call '$.jGrowl', 'Something went wrong. Couldn\'t update record.' end end end end end def enable respond_to do |format| if @credit.enable format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.js do update_credit_partials end else format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.js do render :update do |page| page.call '$.jGrowl', 'Something went wrong. Couldn\'t update record.' end end end end end def update_credit_partials @project = Project.find(params[:project_id]) @credits = @project.fetch_credits(params[:with_subprojects]) @active_credits = @credits.find_all{|credit| credit.enabled == true && credit.settled_on.nil? == true }.group_by{|credit| credit.owner_id} render :update do |page| page.replace_html "my_credits_partial", :partial => 'credits/my_credits' page.replace_html "credit_queue_partial", :partial => 'credits/credit_queue' page.replace_html "credit_history_partial", :partial => 'credits/credit_history' page.replace_html "active_credits_partial", :partial => 'credits/credit_breakdown', :locals => {:group_credits => @active_credits, :title => l(:label_active_credits)} page.visual_effect :highlight, "q_#{@credit.id}", :duration => 2 page.visual_effect :highlight, "h_#{@credit.id}", :duration => 2 page.visual_effect :highlight, "m_#{@credit.id}", :duration => 3 end end def destroy @credit = Credit.find(params[:id]) @credit.destroy respond_to do |format| format.html { redirect_to :controller => :projects, :id => @credit.project_id, :action => "credits" } format.xml { head :ok } end end private def find_credit @credit = Credit.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end def self_authorize if User.current.id != @credit.owner_id render_403 return false end end end ================================================ FILE: app/controllers/documents_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class DocumentsController < ApplicationController default_search_scope :documents before_filter :find_project, :only => [:index, :new] before_filter :find_document, :except => [:index, :new] before_filter :authorize ssl_required :all helper :attachments log_activity_streams :current_user, :name, :added, :@document, :title, :new, :documents, {:object_description_method => :description} log_activity_streams :current_user, :name, :updated, :@document, :title, :edit, :documents, {:object_description_method => :description} log_activity_streams :current_user, :name, :deleted, :@document, :title, :destroy, :documents, {:object_description_method => :description} def index @sort_by = %w(catego»ry date title author).include?(params[:sort_by]) ? params[:sort_by] : 'title' documents = @project.documents.find :all, :include => [:attachments] case @sort_by when 'date' @grouped = documents.group_by {|d| d.updated_at.to_date } when 'title' @grouped = documents.group_by {|d| d.title.first.upcase} when 'author' @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author} else @grouped = documents.group_by {|d| d.updated_at.to_date } end @document = @project.documents.build render :layout => false if request.xhr? end def show @attachments = @document.attachments.find(:all, :order => "created_at DESC") end def new @document = @project.documents.build(params[:document]) if request.post? and @document.save attach_files(@document, params[:attachments]) flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'index', :project_id => @project end end def edit if request.post? and @document.update_attributes(params[:document]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'show', :id => @document end end def destroy @document.destroy redirect_to :controller => 'documents', :action => 'index', :project_id => @project end def add_attachment attachments = attach_files(@document, params[:attachments]) Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added') redirect_to :action => 'show', :id => @document end private def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_document @document = Document.find(params[:id]) @project = @document.project rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/email_updates_controller.rb ================================================ class EmailUpdatesController < ApplicationController before_filter :require_login ssl_required :all def new @email_update = EmailUpdate.new respond_to do |format| format.html format.xml { render :xml => @invitation } end end def create @email_update = EmailUpdate.new(params[:email_update]) @email_update.user = User.current respond_to do |format| if @email_update.save @email_update.send_activation format.html { redirect_with_flash :success, "Please check #{@email_update.mail} for the activation email", {:controller => :my, :action => "account"} } else flash.now[:error] = "Couldn't create email update" format.html { render :action => "new" } end end end def activate @email_update = EmailUpdate.find_by_token(params[:token]) if @email_update.nil? redirect_with_flash :error, l(:error_bad_email_update), :controller => :my, :action => :account return end @email_update.accept redirect_with_flash :success, l(:text_email_updated), :controller => :my, :action => :account return rescue ActiveRecord::RecordNotFound redirect_with_flash :error, l(:error_bad_email_update), :controller => :my, :action => :account end end ================================================ FILE: app/controllers/enterprises_controller.rb ================================================ class EnterprisesController < ApplicationController ssl_required :all def index @enterprises = Enterprise.all respond_to do |format| format.html format.xml { render :xml => @enterprises } end end def show @enterprise = Enterprise.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @enterprise } end end def new @enterprise = Enterprise.new respond_to do |format| format.html format.xml { render :xml => @enterprise } end end def edit @enterprise = Enterprise.find(params[:id]) end def create @enterprise = Enterprise.new(params[:enterprise]) respond_to do |format| if @enterprise.save flash.now[:success] = 'Enterprise was successfully created.' format.html { redirect_to(@enterprise) } format.xml { render :xml => @enterprise, :status => :created, :location => @enterprise } else format.html { render :action => "new" } format.xml { render :xml => @enterprise.errors, :status => :unprocessable_entity } end end end def update @enterprise = Enterprise.find(params[:id]) respond_to do |format| if @enterprise.update_attributes(params[:enterprise]) flash.now[:success] = 'Enterprise was successfully updated.' format.html { redirect_to(@enterprise) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @enterprise.errors, :status => :unprocessable_entity } end end end def destroy @enterprise = Enterprise.find(params[:id]) @enterprise.destroy respond_to do |format| format.html { redirect_to(enterprises_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/enumerations_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class EnumerationsController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all def index list render :action => 'list' end # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list } def list end def new begin @enumeration = params[:type].constantize.new rescue NameError @enumeration = Enumeration.new end end def create @enumeration = Enumeration.new(params[:enumeration]) @enumeration.type = params[:enumeration][:type] if @enumeration.save flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'list', :type => @enumeration.type else render :action => 'new' end end def edit @enumeration = Enumeration.find(params[:id]) end def update @enumeration = Enumeration.find(params[:id]) @enumeration.type = params[:enumeration][:type] if params[:enumeration][:type] if @enumeration.update_attributes(params[:enumeration]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'list', :type => @enumeration.type else render :action => 'edit' end end def destroy @enumeration = Enumeration.find(params[:id]) if !@enumeration.in_use? # No associated objects @enumeration.destroy redirect_to :action => 'index' elsif params[:reassign_to_id] if reassign_to = Enumeration.find_by_type_and_id(@enumeration.type, params[:reassign_to_id]) @enumeration.destroy(reassign_to) redirect_to :action => 'index' end end @enumerations = Enumeration.find(:all, :conditions => ['type = (?)', @enumeration.type]) - [@enumeration] end end ================================================ FILE: app/controllers/help_controller.rb ================================================ class HelpController < ApplicationController def show @help_key = params[:key] end end ================================================ FILE: app/controllers/help_sections_controller.rb ================================================ class HelpSectionsController < ApplicationController before_filter :authorize, :except => :dont_show ssl_required :all def show respond_to do |format| if @help_section.show format.html format.xml { render :xml => @help_section } else format.html { render :nothing => true} end end end def dont_show @help_section = HelpSection.find(params[:id]) @help_section.show = false @help_section.save respond_to do |wants| wants.js { render :update do |page| page.replace "help_section", "" end } end end def new @help_section = HelpSection.new respond_to do |format| format.html format.xml { render :xml => @help_section } end end def edit @help_section = HelpSection.find(params[:id]) end def create @help_section = HelpSection.new(params[:help_section]) respond_to do |format| if @help_section.save flash.now[:success] = 'HelpSection was successfully created.' format.html { redirect_to(@help_section) } format.xml { render :xml => @help_section, :status => :created, :location => @help_section } else format.html { render :action => "new" } format.xml { render :xml => @help_section.errors, :status => :unprocessable_entity } end end end def update @help_section = HelpSection.find(params[:id]) respond_to do |format| if @help_section.update_attributes(params[:help_section]) flash.now[:success] = 'HelpSection was successfully updated.' format.html { redirect_to(@help_section) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @help_section.errors, :status => :unprocessable_entity } end end end def destroy @help_section = HelpSection.find(params[:id]) @help_section.destroy respond_to do |format| format.html { redirect_to(help_sections_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/home_controller.rb ================================================ class HomeController < ApplicationController ssl_required :index layout 'static' def index if User.current.logged? redirect_to :controller => 'welcome', :action => 'index' else redirect_to "/front/index.html" end end def show render :action => params[:page] end def robots @projects = Project.all_public.active render :layout => false, :content_type => 'text/plain' end end ================================================ FILE: app/controllers/hourly_types_controller.rb ================================================ class HourlyTypesController < ApplicationController before_filter :find_project ssl_required :all def new @hourly_type = HourlyType.new(params[:hourly_type]) @hourly_type.project = @project if request.post? && @hourly_type.save flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'hourly_types' end end def edit if request.post? && @hourly_type.update_attributes(params[:hourly_type]) redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'hourly_types' end end def destroy @hourly_type.destroy redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'hourly_types' end private def find_project @project = Project.find(params[:project_id]) @hourly_type = @project.hourly_types.find(params[:id]) if params[:id] rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/invitations_controller.rb ================================================ class InvitationsController < ApplicationController before_filter :find_project, :except => :accept before_filter :authorize, :except => :accept ssl_required :all def index @all_invites, @invitations = paginate :invitations, :per_page => 30, :conditions => {:user_id => User.current.id, :project_id => @project.id}, :order => "created_at DESC" respond_to do |format| format.html { render :layout => false if request.xhr? } format.xml { render :xml => @invitations.to_xml } format.json { render :json => @invitation.to_json } end end def show @invitation = Invitation.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @invitation } end end def new unless @project.root? render_error("Project is not root. No invitations needed here.") return end @note = l(:text_invitation_note_default, {:user => User.current.name, :project => @project.name}) respond_to do |format| format.html format.xml { render :xml => @invitation } end end def edit @invitation = Invitation.find(params[:id]) end def create #can't invite someone to anything other than contributor if you're not admin if params[:invitation][:role_id] != Role.contributor.id.to_s && !User.current.admin_of?(@project) render_403 return end success = false @emails = params[:emails] @email_array = @emails.gsub(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i) {|s| s}.collect @email_array.uniq! @email_array.each do |email| @email_array.delete email unless valid_email?(email) end @email_array.each do |email| @invitation = Invitation.new(params[:invitation]) @invitation.mail = TMail::Address.parse(email).to_s @invitation.project_id = @project.id @invitation.user_id = User.current.id if @invitation.save @invitation.deliver(simple_format_without_paragraph(params[:note])) success = true end end respond_to do |format| if success @emails = nil @note = params[:note] @roles = Role.find(:all, :conditions => {:level => 1}, :order => "position DESC") flash.now[:success] = "#{@email_array.length} invitation(s) successfully sent to
" + @email_array.join(", ") format.html { render :action => "new" } format.xml { render :xml => @invitation, :status => :created, :location => @invitation } else flash.now[:error] = "Failed to send invitations. Make sure emails are properly formatted, and are each on a seperate line" format.html { render :action => "new" } format.xml { render :xml => @invitation.errors, :status => :unprocessable_entity } end end end def accept @invitation = Invitation.find(params[:id]) if @invitation.token != params[:token] || @invitation.status != Invitation::PENDING redirect_with_flash :error, l(:error_old_invite), :controller => :projects, :action => :show, :id => @invitation.project_id return end if @invitation.new_mail && !@invitation.new_mail.empty? @user = User.find_by_mail(@invitation.new_mail) else @user = User.find_by_mail(@invitation.mail) end respond_to do |wants| wants.html { if @user && !@user.anonymous? self.logged_user = @user Track.log(Track::LOGIN,request.env['REMOTE_ADDR']) @invitation.accept msg = "Invitation accepted. You are now a #{@invitation.role.name} of #{@invitation.project.name}." redirect_with_flash :success, msg, :controller => :projects, :action => :show, :id => @invitation.project_id return else session[:invitation] = @invitation.token redirect_to :controller => :account, :action => :register, :invitation_token => @invitation.token end } end end def resend @invitation = Invitation.find(params[:id]) respond_to do |format| if @invitation.resend(params[:note]) logger.info { "1" } format.js do logger.info { "format" } render :update do |page| logger.info { "ID BABY #{@invitation.id}" } page.visual_effect :highlight, "row-#{@invitation.id}", :duration => 3 page.replace "resend-#{@invitation.id}", "Resent!" page.call '$.jGrowl', l(:notice_successful_update) end end else format.js do render :update do |page| page.parent.call '$.jGrowl', l(:error_general) end end end end end def destroy @invitation = Invitation.find(params[:id]) @invitation.destroy respond_to do |format| format.js do render :update do |page| page.visual_effect :highlight, "row-#{@invitation.id}", :duration => 3 page.remove "row-#{@invitation.id}" page.call '$.jGrowl', l(:notice_successful_delete) end end end end private def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def valid_email?(email) TMail::Address.parse(email) return true rescue return false end end ================================================ FILE: app/controllers/issue_invitations_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssueInvitationsController < ApplicationController def new @quote = Quote.new respond_to do |format| format.html format.xml { render :xml => @quote } end end def create @quote = Quote.new(params[:quote]) @quote.user_id = User.current.id respond_to do |format| flash.now[:success] = 'Invitation was successfully created and sent' format.html { redirect_to(@quote) } format.xml { render :xml => @quote, :status => :created, :location => @quote } end end end ================================================ FILE: app/controllers/issue_relations_controller.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # class IssueRelationsController < ApplicationController before_filter :find_project, :authorize ssl_required :all def new @relation = IssueRelation.new(params[:relation]) @relation.issue_from = @issue if params[:relation] && !params[:relation][:issue_to_id].blank? @relation.issue_to = Issue.visible.find_by_id(params[:relation][:issue_to_id]) end @relation.save if request.post? respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.js do render :update do |page| page.replace_html "relations", :partial => 'issues/relations' if @relation.errors.empty? page << "$('relation_delay').value = ''" page << "$('relation_issue_to_id').value = ''" end end end end end def destroy relation = IssueRelation.find(params[:id]) if request.post? && @issue.relations.include?(relation) relation.destroy @issue.reload end respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} } end end private def find_project @issue = Issue.find(params[:issue_id]) @project = @issue.project rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/issue_statuses_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssueStatusesController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all verify :method => :post, :only => [ :destroy, :create, :update, :move ], :redirect_to => { :action => :list } def index list render :action => 'list' unless request.xhr? end def list @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position" render :action => "list", :layout => false if request.xhr? end def new @issue_status = IssueStatus.new end def create @issue_status = IssueStatus.new(params[:issue_status]) if @issue_status.save flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'list' else render :action => 'new' end end def edit @issue_status = IssueStatus.find(params[:id]) end def update @issue_status = IssueStatus.find(params[:id]) if @issue_status.update_attributes(params[:issue_status]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'list' else render :action => 'edit' end end def destroy IssueStatus.find(params[:id]).destroy redirect_to :action => 'list' rescue flash.now[:error] = "Unable to delete issue status" redirect_to :action => 'list' end end ================================================ FILE: app/controllers/issue_votes_controller.rb ================================================ class IssueVotesController < ApplicationController ssl_required :all def index @issue_votes = IssueVote.all respond_to do |format| format.html format.xml { render :xml => @issue_votes } end end def show @issue_vote = IssueVote.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @issue_vote } end end def new @issue_vote = IssueVote.new respond_to do |format| format.html format.xml { render :xml => @issue_vote } end end def edit @issue_vote = IssueVote.find(params[:id]) end def create @issue_vote = IssueVote.new(params[:issue_vote]) respond_to do |format| if @issue_vote.save flash.now[:success] = 'IssueVote was successfully created.' format.html { redirect_to(@issue_vote) } format.xml { render :xml => @issue_vote, :status => :created, :location => @issue_vote } else format.html { render :action => "new" } format.xml { render :xml => @issue_vote.errors, :status => :unprocessable_entity } end end end def update @issue_vote = IssueVote.find(params[:id]) respond_to do |format| if @issue_vote.update_attributes(params[:issue_vote]) flash.now[:success] = 'IssueVote was successfully updated.' format.html { redirect_to(@issue_vote) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @issue_vote.errors, :status => :unprocessable_entity } end end end def destroy @issue_vote = IssueVote.find(params[:id]) @issue_vote.destroy respond_to do |format| format.html { redirect_to(issue_votes_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/issues_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssuesController < ApplicationController menu_item :new_issue, :only => :new default_search_scope :issues ssl_required :all # BUGBUG: :disagree and :reject don't seem to be used anymore before_filter :find_issue, :only => [:show, :edit, :reply, :start, :finish, :release, :cancel, :restart, :prioritize, :agree, :disagree, :accept, :reject, :estimate, :join, :leave, :add_team_member, :remove_team_member, :move, :update_tags] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] before_filter :find_project, :only => [:new, :update_form, :preview] before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu, :datadump, :temp] before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] accept_key_auth :index, :show, :changes rescue_from Query::StatementInvalid, :with => :query_statement_invalid helper :journals helper :projects include ProjectsHelper helper :issue_relations include IssueRelationsHelper helper :watchers include WatchersHelper helper :attachments include AttachmentsHelper helper :queries helper :sort include SortHelper include IssuesHelper include Redmine::Export::PDF verify :method => :post, :only => :destroy, :render => { :nothing => true, :status => :method_not_allowed} log_activity_streams :current_user, :name, :added, :@issue, :subject, :new, :issues, {:object_description_method => :description} log_activity_streams :current_user, :name, :finished, :@issue, :subject, :finish, :issues, { :object_description_method => :description, :indirect_object => :@journal, :indirect_object_description_method => :notes, :indirect_object_phrase => '' } log_activity_streams :current_user, :name, :started, :@issue, :subject, :start, :issues, {:object_description_method => :description} log_activity_streams :current_user, :name, :gave_up_on, :@issue, :subject, :release, :issues, {:object_description_method => :description} log_activity_streams :current_user, :name, :canceled, :@issue, :subject, :cancel, :issues, { :object_description_method => :description, :indirect_object => :@journal, :indirect_object_description_method => :notes, :indirect_object_phrase => '' } log_activity_streams :current_user, :name, :joined, :@issue, :subject, :join, :issues, {:object_description_method => :description} log_activity_streams :current_user, :name, :left, :@issue, :subject, :leave, :issues, {:object_description_method => :description} log_activity_streams :current_user, :name, :updated, :@issue, :subject, :edit, :issues,{ :object_description_method => :description, :indirect_object => :@journal, :indirect_object_description_method => :notes, :indirect_object_phrase => 'GENERATEDETAILS' } #special value generates details for each property change log_activity_streams :current_user, :name, :restarted, :@issue, :subject, :restart, :issues, {} def index retrieve_query sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h})) if @query.valid? limit = per_page_option respond_to do |format| format.html { } format.csv { limit = Setting.issues_export_limit.to_i } format.pdf { limit = Setting.issues_export_limit.to_i } end @issue_count = @query.issue_count @issue_pages = Paginator.new self, @issue_count, limit, params['page'] @issues = @query.issues(:include => [:assigned_to, :tracker], :order => sort_clause, :offset => @issue_pages.current.offset, :limit => limit) @issue_count_by_group = @query.issue_count_by_group respond_to do |format| format.html { render :template => 'issues/index.html.erb', :layout => !request.xhr? } format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') } format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } end else # Send html if the query is not valid render(:template => 'issues/index.html.erb', :layout => !request.xhr?) end rescue ActiveRecord::RecordNotFound render_404 end def show @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_at ASC") @journals.each_with_index {|j,i| j.indice = i+1} @journals.reverse! if User.current.wants_comments_in_reverse_order? @edit_allowed = true @edit_allowed = @issue.editable? && User.current.allowed_to?(:edit_issues, @project) respond_to do |format| format.html { render :template => 'issues/show.html.erb', :layout => 'issue_blank' } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } end end # Add a new issue # The new issue will be created from an existing one if copy_from parameter is given def new @issue = Issue.new @issue.copy_from(params[:copy_from]) if params[:copy_from] @issue.project = @project @issue.tracker ||= Tracker.find(params[:tracker_id] || :first || params[:issue][:tracker_id]) if @issue.tracker.nil? render_error l(:error_no_tracker_in_project) return end if params[:issue].is_a?(Hash) @issue.attributes = params[:issue] @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project) end @issue.author = User.current default_status = IssueStatus.default unless default_status render_error l(:error_no_default_issue_status) return end @issue.status = default_status @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq if request.get? @issue.start_date ||= Date.today else if params[:issue].nil? || params[:issue][:status_id].nil? requested_status = default_status else requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) end @issue.status = requested_status @issue.tag_list = @issue.tags_copy if @issue.tags_copy if @issue.save Mention.parse(@issue, User.current.id) attach_files_for_new_issue(@issue, params[:attachments]) #adding self-agree vote @iv = IssueVote.create :issue_id => @issue.id, :user_id => User.current.id, :points => 1, :vote_type => IssueVote::AGREE_VOTE_TYPE @issue.update_agree_total @iv.isbinding #dealing with the estimate if params[:estimate] && params[:estimate] != "" #-2 means that nothing was chosen @iv = IssueVote.create :issue_id => @issue.id, :user_id => User.current.id, :points => params[:estimate].to_i, :vote_type => IssueVote::ESTIMATE_VOTE_TYPE @issue.update_estimate_total @iv.isbinding end #dealing with prioritizing if params[:prioritize] == "true" @iv = IssueVote.create :issue_id => @issue.id, :user_id => User.current.id, :points => 1, :vote_type => IssueVote::PRI_VOTE_TYPE @issue.update_pri_total @iv.isbinding end @issue.save if !@issue.update_status @issue.reload respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } : { :action => 'show', :id => @issue })} end return else respond_to do |format| format.js {render :text => nil} format.html { render :action => "new" } end return end end render :layout => !request.xhr? end # Attributes that can be updated on workflow transition (without :edit permission) # TODO: make it configurable (at least per role) UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION) def edit @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = @issue.editable? && User.current.allowed_to?(:edit_issues, @project) @notes = params[:notes] @journal = @issue.init_journal(User.current, @notes) # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue] attrs = params[:issue].dup attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s} @issue.attributes = attrs end if request.post? attachments = attach_files(@issue, params[:attachments]) attachments.each {|a| @journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} if @issue.save Mention.parse(@issue, User.current.id) @issue.reload respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end end rescue ActiveRecord::StaleObjectError # Optimistic locking exception flash.now[:error] = l(:notice_locking_conflict) # Remove the previously added attachments if issue was not updated attachments.each(&:destroy) end def start @in_progress = Issue.count(:conditions => {:assigned_to_id => User.current.id, :status_id => IssueStatus.assigned.id, :project_id => @issue.project_id}) if @in_progress >= Setting::MAXIMUM_CONCURRENT_REQUESTS render_error "Maximum issues owned by this user already" return false; else IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::JOIN_VOTE_TYPE, :points => 1 #Joins as first person on the team IssueVote.delete_all :issue_id => params[:id], :vote_type => IssueVote::ACCEPT_VOTE_TYPE params[:issue] = {:status_id => IssueStatus.assigned.id, :assigned_to_id => User.current.id} change_status end end def finish params[:issue] = {:status_id => IssueStatus.done.id} @iv = IssueVote.create :user_id => User.current.id, :issue_id => @issue.id, :vote_type => IssueVote::ACCEPT_VOTE_TYPE, :points => 1 #adding accept vote for user who finished the issue @issue.update_accept_total @iv.isbinding @issue.clone_recurring if @issue.tracker.recurring? @issue.set_points_from_hourly if @issue.is_hourly? #an hourly item is done, we set the change_status end def release if(@issue.is_hourly?) params[:issue] = {:status_id => IssueStatus.newstatus.id, :assigned_to_id => ''} else #Deleting current user from issue IssueVote.delete_all(["user_id = ? AND issue_id = ? AND vote_type = ?", User.current.id, params[:id], IssueVote::JOIN_VOTE_TYPE]) #Check to see if anybody else is on the issue, if they are assign the issue to them next_team_member = @issue.team_members.first if next_team_member.nil? new_status_id = IssueStatus.open.id params[:issue] = {:status_id => IssueStatus.open.id, :assigned_to_id => ''} else params[:issue] = {:assigned_to_id => next_team_member.id} end end change_status end def cancel params[:issue] = {:status_id => IssueStatus.canceled.id} change_status end def restart params[:issue] = {:status_id => IssueStatus.newstatus.id} change_status end def change_status @notes = params[:notes] @journal = @issue.init_journal(User.current, @notes) attrs = params[:issue].dup @issue.attributes = attrs if @issue.save respond_to do |format| @issue.reload format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end rescue ActiveRecord::StaleObjectError # Optimistic locking exception flash.now[:error] = l(:notice_locking_conflict) end def prioritize @iv = IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::PRI_VOTE_TYPE, :points => params[:points] @issue.update_pri_total @iv.isbinding @issue.save @issue.reload respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def update_tags @issue.send_later(:update_tags,params[:tags]) respond_to do |format| format.js {render :nothing => true} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def estimate if(@issue.is_hourly?) render_error 'Can not estimate hourly items' return false; end @journal = @issue.init_journal(User.current, params["notes"]) @iv = IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::ESTIMATE_VOTE_TYPE, :points => params[:points] logger.info { "before update #{@issue.inspect}" } @issue.update_estimate_total @iv.isbinding logger.info { "after update #{@issue.inspect}" } logger.info { "start saving" } @issue.save if !@issue.update_status logger.info { "done saving #{@issue.inspect}" } @issue.reload respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def agree @iv = IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::AGREE_VOTE_TYPE, :points => params[:points] journal = @issue.init_journal(User.current, params[:notes]) if params[:notes] @issue.update_agree_total @iv.isbinding @issue.save if !@issue.update_status @issue.reload if params[:notes] action = :updated logger.info { "action is #{params[:points]}" } case params[:points] when "-1" action = "voted against" when "-9999" action = :blocked end LogActivityStreams.write_single_activity_stream(User.current,:name,@issue,:subject,action,:issues, 0, journal,{ :indirect_object_description_method => :notes, :indirect_object_phrase => 'GENERATEDETAILS' }) end respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def accept @iv = IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::ACCEPT_VOTE_TYPE, :points => params[:points] journal = @issue.init_journal(User.current, params[:notes]) if params[:notes] @issue.update_accept_total @iv.isbinding @issue.save if !@issue.update_status @issue.reload if params[:notes] action = :updated logger.info { "action is #{params[:points]}" } case params[:points] when "-1" action = :rejected when "-9999" action = "blocked acceptance of" end LogActivityStreams.write_single_activity_stream(User.current,:name,@issue,:subject,action,:issues, 0, journal,{ :indirect_object_description_method => :notes, :indirect_object_phrase => 'GENERATEDETAILS' }) end respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def join IssueVote.create :user_id => User.current.id, :issue_id => params[:id], :vote_type => IssueVote::JOIN_VOTE_TYPE, :points => 1 @issue.save @issue.reload Notification.create :recipient_id => @issue.assigned_to_id, :variation => 'issue_joined', :params => {:issue_id => @issue.id}, :sender_id => User.current.id, :source_id => @issue.id, :source_type => "Issue" respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def add_team_member IssueVote.create :user_id => params[:issue_vote][:user_id], :issue_id => params[:id], :vote_type => IssueVote::JOIN_VOTE_TYPE, :points => 1 @issue.save @issue.reload Notification.create :recipient_id => params[:issue_vote][:user_id], :variation => 'issue_team_member_added', :params => {:issue_id => @issue.id, :joiner_id => params[:issue_vote][:user_id]}, :sender_id => User.current.id, :source_id => @issue.id, :source_type => "Issue" respond_to do |format| format.js do render :update do |page| page.replace "joined_by_partial", :partial => 'issues/joined_by' page.visual_effect :highlight, "joined_by_partial" end end end end def remove_team_member IssueVote.delete_all :user_id => params[:user_id], :issue_id => params[:id], :vote_type => IssueVote::JOIN_VOTE_TYPE Notification.create :recipient_id => params[:user_id], :variation => 'issue_team_member_removed', :params => {:issue_id => @issue.id, :joiner_id => params[:user_id]}, :sender_id => User.current.id, :source_id => @issue.id, :source_type => "Issue" respond_to do |format| format.js do render :update do |page| page.replace "joined_by_partial", :partial => 'issues/joined_by' page.visual_effect :highlight, "joined_by_partial" end end end end def leave IssueVote.delete_all(["user_id = ? AND issue_id = ? AND vote_type = ?", User.current.id, params[:id], IssueVote::JOIN_VOTE_TYPE]) @issue.save @issue.reload admin = User.sysadmin Notification.create :recipient_id => @issue.assigned_to_id, :variation => 'issue_left', :params => {:issue => @issue, :joiner => User.current}, :sender_id => User.current.id, :source_id => @issue.id, :source_type => "Issue" respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html {redirect_to(params[:back_to] || {:action => 'show', :id => @issue})} end end def reply journal = Journal.find(params[:journal_id]) if params[:journal_id] if journal user = journal.user text = journal.notes else user = @issue.author text = @issue.description end content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " content << text.to_s.strip.gsub(%r{
((.|\s)*?)
}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" render(:update) { |page| page.<< "$('notes').value = \"#{content}\";" page.show 'update' page << "$('#notes').focus();" page << "$('body').scrollTo('#update');" } end # Bulk edit a set of issues def bulk_edit if request.post? tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id]) status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id]) assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id]) unsaved_issue_ids = [] @issues.each do |issue| journal = issue.init_journal(User.current, params[:notes]) issue.tracker = tracker if tracker issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none' issue.start_date = params[:start_date] unless params[:start_date].blank? issue.due_date = params[:due_date] unless params[:due_date].blank? issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank? # Don't save any change to the issue if the user is not authorized to apply the requested status unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save # Keep unsaved issue ids to display them in flash error unsaved_issue_ids << issue.id end end if unsaved_issue_ids.empty? flash.now[:success] = l(:notice_successful_update) unless @issues.empty? else flash.now[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size, :total => @issues.size, :ids => '#' + unsaved_issue_ids.join(', #')) end redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project}) return end @available_statuses = Workflow.available_statuses(@project) end def move @copy = params[:copy_options] && params[:copy_options][:copy] @allowed_projects = [] # find projects to which the user is allowed to move the issue if User.current.admin? # admin is allowed to move issues to any active (visible) project @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current)) else @issue.project.root.self_and_descendants.each {|p| @allowed_projects << p if p.visible_to(User.current)} end @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] @target_project ||= @project @trackers = @target_project.trackers @available_statuses = Workflow.available_statuses(@project) if request.post? new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) unsaved_issue_ids = [] moved_issues = [] @issues.each do |issue| changed_attributes = {} [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute| unless params[valid_attribute].blank? changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute]) end end issue.init_journal(User.current) if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes}) LogActivityStreams.write_single_activity_stream(User.current,:name,issue,:subject,:moved,:move, 0, @target_project, { :indirect_object_name_method => :name, :indirect_object_description_method => :name, :indirect_object_phrase => 'to ' }) moved_issues << r else unsaved_issue_ids << issue.id end end @project.project.send_later :refresh_issue_count if unsaved_issue_ids.empty? else flash.now[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size, :total => @issues.size, :ids => '#' + unsaved_issue_ids.join(', #')) end if params[:follow] if @issues.size == 1 && moved_issues.size == 1 redirect_to :controller => 'projects', :action => 'dashboard', :id => (@target_project || @project), :show_issue_id => moved_issues.first else redirect_to :controller => 'projects', :action => 'dashboard', :id => (@target_project || @project) end else redirect_to :controller => 'projects', :action => 'dashboard', :id => @project end return end render :layout => false if request.xhr? end def destroy @issues.each(&:destroy) redirect_to :action => 'index', :project_id => @project end def gantt @gantt = Redmine::Helpers::Gantt.new(params) retrieve_query if @query.valid? events = [] # Issues that have start and due dates events += @query.issues(:include => [:tracker, :assigned_to], :order => "start_date, due_date", :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] ) # Issues that don't have a due date but that are assigned to a version with a date events += @query.issues(:include => [:tracker, :assigned_to], :order => "start_date, effective_date", :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] ) @gantt.events = events end basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' respond_to do |format| format.html { render :template => "issues/gantt.html.erb", :layout => !request.xhr? } format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") } end end def calendar if params[:year] and params[:year].to_i > 1900 @year = params[:year].to_i if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 @month = params[:month].to_i end end @year ||= Date.today.year @month ||= Date.today.month @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) retrieve_query if @query.valid? events = [] events += @query.issues(:include => [:tracker, :assigned_to], :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] ) @calendar.events = events end render :layout => false if request.xhr? end def context_menu @issues = Issue.find_all_by_id(params[:ids], :include => :project) if (@issues.size == 1) @issue = @issues.first @allowed_statuses = @issue.new_statuses_allowed_to(User.current) end projects = @issues.collect(&:project).compact.uniq @project = projects.first if projects.size == 1 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), :move => (@project && User.current.allowed_to?(:move_issues, @project)), :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) } if @project @assignables = @project.assignable_users @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) @trackers = @project.trackers end @statuses = IssueStatus.find(:all, :order => 'position') @back = params[:back_url] || request.env['HTTP_REFERER'] render :layout => false end def update_form if params[:id].blank? @issue = Issue.new @issue.project = @project else @issue = @project.issues.visible.find(params[:id]) end @issue.attributes = params[:issue] @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq render :partial => 'attributes' end def preview @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? @attachements = @issue.attachments if @issue @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil) render :partial => 'common/preview' end def datadump @issues = Issue.find(:all, :conditions => "project_id IN (#{User.current.owned_projects.collect {|p| p.id}.join(",")})") render :csv => @issues end private def find_issue @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author]) @project = @issue.project render_message l(:text_project_locked) if @project.locked? render_404 if @issue.is_gift? && @issue.assigned_to_id == User.current.id rescue ActiveRecord::RecordNotFound render_404 end # Filter for bulk operations def find_issues @issues = Issue.find_all_by_id(params[:id] || params[:ids]) raise ActiveRecord::RecordNotFound if @issues.empty? projects = @issues.collect(&:project).compact.uniq if projects.size == 1 @project = projects.first else # TODO: let users bulk edit/move/destroy issues from different projects render_error 'Can not bulk edit/move/destroy issues from different projects' return false end rescue ActiveRecord::RecordNotFound render_404 end def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_optional_project @project = Project.find(params[:project_id]) unless params[:project_id].blank? allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true) allowed ? true : deny_access rescue ActiveRecord::RecordNotFound render_404 end # Retrieve query from session or build a new query def retrieve_query if !params[:query_id].blank? cond = "project_id IS NULL" cond << " OR project_id = #{@project.id}" if @project @query = Query.find(params[:query_id], :conditions => cond) @query.project = @project session[:query] = {:id => @query.id, :project_id => @query.project_id} sort_clear else if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) # Give it a name, required to be valid @query = Query.new(:name => "_") @query.project = @project if params[:fields] and params[:fields].is_a? Array params[:fields].each do |field| @query.add_filter(field, params[:operators][field], params[:values][field]) end else @query.available_filters.keys.each do |field| @query.add_short_filter(field, params[field]) if params[field] end end @query.group_by = params[:group_by] @query.column_names = params[:query] && params[:query][:column_names] session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} else @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query.project = @project end end end # Rescues an invalid query statement. Just in case... def query_statement_invalid(exception) logger.error "Query::StatementInvalid: #{exception.message}" if logger session.delete(:query) sort_clear render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." end end ================================================ FILE: app/controllers/journals_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # class JournalsController < ApplicationController before_filter :find_journal ssl_required :all def edit if request.post? @journal.update_attributes(:notes => params[:notes]) if params[:notes] if @journal.details.empty? && @journal.notes.blank? @journal.destroy else update_activity_stream(params[:notes]) if params[:notes] end respond_to do |format| format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } format.js { render :action => 'update' } end end end def edit_from_dashboard if @journal.update_attributes(params[:journal]) update_activity_stream(params[:journal][:notes]) end respond_to do |format| format.js {render :json => @journal.issue.to_dashboard} end end private def update_activity_stream(notes) ActivityStream.update_all(["indirect_object_description = ?", notes], {:indirect_object_id => @journal.id, :indirect_object_type => "Journal", :object_type => "Issue", :actor_id => User.current.id}, :order => 'created_at DESC', :limit => 1) end def find_journal @journal = Journal.find(params[:id]) (render_403; return false) unless @journal.editable_by?(User.current) @project = @journal.journalized.project rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/mail_handler_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # class MailHandlerController < ActionController::Base before_filter :check_credential verify :method => :post, :only => :index, :render => { :nothing => true, :status => 405 } # Submits an incoming email to MailHandler def index options = params.dup email = options.delete(:email) if MailHandler.receive(email, options) render :nothing => true, :status => :created else render :nothing => true, :status => :unprocessable_entity end end # Submits an incoming email from sendgrid to MailHandler def sendgrid @email = TMail::Mail.new @email.subject = params[:subject] @email.body = params[:text].to_s.gsub(/"/,'\"') @email.to = params[:to] @email.from = params[:from] @email.subject = params[:subject] logger.info { "email coming up" } logger.info(@email.inspect) # attachments - Number of attachments included in email # * # attachment1, attachment2, …, attachmentN - File upload names. The numbers are sequence numbers starting from 1 and ending on the number specified by the attachments parameter. If attachments is 0, there will be no attachment files. If attachments is 3, parameters attachment1, attachment2, and attachment3 will have file uploads. if MailHandler.receive_from_api(@email) render :nothing => true, :status => :created else render :nothing => true, :status => :unprocessable_entity end end private def check_credential User.current = nil unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403 end end end ================================================ FILE: app/controllers/mails_controller.rb ================================================ class MailsController < ApplicationController before_filter :set_user def index if params[:mailbox] == "sent" @mails = @user.sent_messages else @mails = @user.received_messages end end def show @mail = Mail.read_and_get(params[:id], User.current) end def new @mail = Mail.new if params[:reply_to] @reply_to = @user.received_messages.find(params[:reply_to]) unless @reply_to.nil? @mail.to = @reply_to.sender.login @mail.subject = "Re: #{@reply_to.subject}" @mail.body = "\n\n*Original message*\n\n #{@reply_to.body}" end end end def create @mail = Mail.new(params[:mail]) @mail.sender = @user @mail.recipient = User.find_by_login(params[:mail][:to]) if @mail.save flash.now[:success] = "Message sent" redirect_to user_mails_path(@user) else render :action => :new end end def delete_selected if request.post? if params[:delete] params[:delete].each { |id| @mail = Mail.find(:first, :conditions => ["mails.id = ? AND (sender_id = ? OR recipient_id = ?)", id, @user, @user]) @mail.mark_deleted(@user) unless @mail.nil? } flash.now[:success] = "Messages deleted" end redirect_to user_mail_path(@user, @mails) end end private def set_user @user = User.current end end ================================================ FILE: app/controllers/members_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class MembersController < ApplicationController before_filter :find_member, :except => [:new, :autocomplete_for_member] before_filter :find_project, :only => [:new, :autocomplete_for_member] before_filter :authorize ssl_required :all def new members = [] if params[:member] && request.post? attrs = params[:member].dup if (user_ids = attrs.delete(:user_ids)) user_ids.each do |user_id| members << Member.new(attrs.merge(:user_id => user_id)) end else members << Member.new(attrs) end result = @project.all_members << members end respond_to do |format| format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members' members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") } } } end end def edit if request.post? and @member.update_attributes(params[:member]) respond_to do |format| format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members' page.visual_effect(:highlight, "member-#{@member.id}") } } end end end def destroy if request.post? && @member.deletable? @member.destroy end respond_to do |format| format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} } end end def autocomplete_for_member @users = User.active.like(params[:q]).find(:all, :limit => 100) - @project.users render :layout => false end private def find_project @project = Project.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end def find_member @member = Member.find(params[:id]) @project = @member.project rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/messages_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class MessagesController < ApplicationController menu_item :boards default_search_scope :messages before_filter :find_board, :only => [:new, :preview] before_filter :find_message, :except => [:new, :preview, :motion_reply] before_filter :authorize, :except => [:preview, :edit, :destroy] ssl_required :all verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show } verify :xhr => true, :only => :quote helper :watchers helper :attachments include AttachmentsHelper log_activity_streams :current_user, :name, :created, :@message, :subject, :new, :messages, {:object_description_method => :content} log_activity_streams :current_user, :name, :edited, :@message, :subject, :edit, :messages, {:object_description_method => :content} log_activity_streams :current_user, :name, :replied_to, :@topic, :subject, :reply, :messages, { :object_description_method => :content, :indirect_object => :@reply, :indirect_object_description_method => :content, :indirect_object_phrase => '' } # Show a topic and its replies def show @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}]) @replies.reverse! if User.current.wants_comments_in_reverse_order? @reply = Message.new(:subject => "RE: #{@message.subject}") render :action => "show", :layout => false if request.xhr? end # Create a new topic def new @message = Message.new(params[:message]) @message.author = User.current @message.board = @board if params[:message] && User.current.allowed_to?(:edit_messages, @project) @message.locked = params[:message]['locked'] @message.sticky = params[:message]['sticky'] end if request.post? && @message.save attach_files(@message, params[:attachments]) redirect_to :action => 'show', :id => @message end end # Reply to a topic def reply @reply = Message.new(params[:reply]) @reply.subject = @message.subject if @reply.subject == "" @reply.author = User.current @reply.board = @board @topic.children << @reply if !@reply.new_record? attach_files(@reply, params[:attachments]) end respond_to do |wants| wants.html { redirect_to :action => 'show', :id => @topic } wants.js { render :nothing => :true} end end # Edit a message def edit (render_403; return false) unless @message.editable_by?(User.current) if params[:message] @message.locked = params[:message]['locked'] @message.sticky = params[:message]['sticky'] end if request.post? && @message.update_attributes(params[:message]) attach_files(@message, params[:attachments]) flash.now[:success] = l(:notice_successful_update) @message.reload redirect_to :action => 'show', :board_id => @message.board, :id => @message.root end end # Delete a messages def destroy (render_403; return false) unless @message.destroyable_by?(User.current) @message.destroy redirect_to @message.parent.nil? ? { :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } : { :action => 'show', :id => @message.parent } end def quote user = @message.author text = @message.content subject = @message.subject.gsub('"', '\"') subject = "RE: #{subject}" unless subject.starts_with?('RE:') content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " content << text.to_s.strip.gsub(%r{
((.|\s)*?)
}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n" render(:update) { |page| page << "$('reply_subject').value = \"#{subject}\";" page.<< "$('message_content').value = \"#{content}\";" page.show 'reply' page << "$('#message_content').focus();" page << "$('body').scrollTo('#reply');" } end def preview message = @board.messages.find_by_id(params[:id]) @attachements = message.attachments if message @text = (params[:message] || params[:reply])[:content] render :partial => 'common/preview' end private def find_message if params[:board_id] == 'guess' logger.info { "guessing board" } guess_board else find_board @message = @board.messages.find(params[:id], :include => :parent) end @topic = @message.root unless @message.nil? rescue ActiveRecord::RecordNotFound render_404 end #This function is used to redirect links coming from the activity stream #To save queries on the database, we don't try to load the board id in the link to a message def guess_board @message = Message.find(params[:id], :include => :parent) @board = @message.board @project = @board.project logger.info { "guessed board #{@board.inspect}" } rescue ActiveRecord::RecordNotFound render_404 end def find_board @board = Board.find(params[:board_id], :include => :project) @project = @board.project rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/motion_votes_controller.rb ================================================ class MotionVotesController < ApplicationController ssl_required :all def index @motion_votes = MotionVote.all respond_to do |format| format.html format.xml { render :xml => @motion_votes } end end def show @motion_vote = MotionVote.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @motion_vote } end end def new @motion_vote = MotionVote.new respond_to do |format| format.html format.xml { render :xml => @motion_vote } end end def edit @motion_vote = MotionVote.find(params[:id]) end def create @motion_vote = MotionVote.new(params[:motion_vote]) @motion_vote.motion_id = params[:motion_id] @motion_vote.user_id = User.current.id if @motion_vote.motion.motion_type == Motion::TYPE_SHARE sum = @motion_vote.user.shares.for_project(@motion_vote.motion.project_id).sum(:amount).to_i @motion_vote.points = params[:points].to_i * sum else @motion_vote.points = params[:points] end respond_to do |format| if @motion_vote.save format.js { render :action => "cast_vote", :motion => @motion_vote.motion} else format.js { render :action => "error"} format.html { render :action => "new" } format.xml { render :xml => @motion_vote.errors, :status => :unprocessable_entity } end end end def update @motion_vote = MotionVote.find(params[:id]) respond_to do |format| if @motion_vote.update_attributes(params[:motion_vote]) flash.now[:success] = 'MotionVote was successfully updated.' format.html { redirect_to(@motion_vote) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @motion_vote.errors, :status => :unprocessable_entity } end end end def destroy @motion_vote = MotionVote.find(params[:id]) @motion_vote.destroy respond_to do |format| format.html { redirect_to(motion_votes_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/motions_controller.rb ================================================ class MotionsController < ApplicationController before_filter :find_project, :only => [:new,:index,:create,:show, :edit, :eligible_users] before_filter :find_motion, :only => [:show, :edit, :destroy, :update, :reply] before_filter :check_visibility_permission, :only => [:show] before_filter :require_admin, :only => [:edit, :update, :destroy] before_filter :authorize, :except => [:check_visibility_permission] ssl_required :all def index @motions = @project.motions respond_to do |format| format.html format.xml { render :xml => @motions } end end def show if @motion.concerned_user_id == User.current.id render_403 return false end @motion.create_forum_topic if @motion.topic.nil? @topic = @motion.topic @board = @topic.board @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}]) @replies.reverse! if User.current.wants_comments_in_reverse_order? @reply = Message.new(:subject => "RE: #{@topic.subject}") respond_to do |format| format.html format.xml { render :xml => @motion } end end def new @motion = Motion.new(params[:motion]) @concerned_user_list = Motion.eligible_users(@motion.variation, @project.id) respond_to do |format| format.html format.xml { render :xml => @motion } end end def eligible_users @concerned_user_list = "" @variation = params[:variation].to_i case @variation when Motion::VARIATION_NEW_MEMBER @concerned_user_list = @project.contributor_list when Motion::VARIATION_NEW_CORE @concerned_user_list = @project.member_list when Motion::VARIATION_FIRE_MEMBER @concerned_user_list = @project.member_list when Motion::VARIATION_FIRE_CORE @concerned_user_list = @project.core_member_list end @concerned_user_list = [] if @concerned_user_list == "" #remove current user from list @concerned_user_list.delete_if {|a| a.user_id == User.current.id} render :layout => false end def edit end def create @motion = Motion.new(params[:motion]) @motion.project_id = @project.id @motion.author_id = User.current.id @motion.params = params[:param] respond_to do |format| if @motion.concerned_user == User.current format.html { redirect_with_flash :error, 'Cannot create a motion concerning yourself', :action => 'index' } format.xml { render :xml => @motion.errors, :status => :unprocessable_entity } elsif !@motion.concerned_user && @motion.concerns_someone? format.html { redirect_with_flash :error, 'Who does this motion apply to? You need to select the user that this motion is concerned with.', :action => 'index' } format.xml { render :xml => @motion.errors, :status => :unprocessable_entity } elsif @motion.save format.html { redirect_with_flash :success, 'Motion was successfully created', :action => "show", :id => @motion } format.xml { render :xml => @motion, :status => :created, :location => @motion } else format.html { render :action => "new" } format.xml { render :xml => @motion.errors, :status => :unprocessable_entity } end end end def update respond_to do |format| if @motion.update_attributes(params[:motion]) flash.now[:success] = 'Motion was successfully updated.' format.html { redirect_to(@motion) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @motion.errors, :status => :unprocessable_entity } end end end def destroy @motion.destroy respond_to do |format| format.html { redirect_to(motions_url) } format.xml { head :ok } end end # Reply to a motion discussion def reply @topic = @motion.topic @reply = Message.new(params[:reply]) @reply.author = User.current @reply.board = @topic.board @topic.children << @reply if !@reply.new_record? attach_files(@reply, params[:attachments]) end redirect_to :action => 'show', :id => @motion, :project_id => @motion.project_id rescue 404 end private def find_project @project = Project.find(params[:project_id]).root render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_motion @motion = Motion.find(params[:id]) @project = @motion.project render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def check_visibility_permission if !User.current.allowed_to_see_motion?(@motion) render_403 return false end return true end end ================================================ FILE: app/controllers/my_controller.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class MyController < ApplicationController before_filter :require_login ssl_required :all helper :issues BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, 'issuesreportedbyme' => :label_reported_issues, 'issueswatched' => :label_watched_issues, 'news' => :label_news_latest, 'calendar' => :label_calendar, 'documents' => :label_document_plural }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'], 'right' => ['issuesreportedbyme'] }.freeze verify :xhr => true, :only => [:add_block, :remove_block, :order_blocks] def index page render :action => 'page' end # Show user's page def page @user = User.current @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT end def projects project_ids = User.current.projects.collect{|p| p.id}.join(",") @all_projects = project_ids.any? ? Project.find(:all, :conditions => "(parent_id in (#{project_ids}) OR id in (#{project_ids})) AND (status=#{Project::STATUS_ACTIVE})") : [] @my_projects = User.current.owned_projects @belong_to_projects = User.current.belongs_to_projects @active_projects = User.current.active_memberships.collect(&:project) end def issues @assigned_issues = Issue.visible.open.find(:all, :conditions => {:assigned_to_id => User.current.id}, :include => [:project, :tracker ], :order => "#{Issue.table_name}.subject ASC") @watched_issues = Issue.visible.find(:all, :include => [:project, :tracker, :watchers], :conditions => ["#{Watcher.table_name}.user_id = ?", User.current.id], :order => "#{Issue.table_name}.subject ASC") @joined_issues = Issue.visible.find(:all, :include => [:project, :tracker, :issue_votes], :conditions => ["#{IssueVote.table_name}.user_id = ? AND #{IssueVote.table_name}.vote_type = ? AND #{Issue.table_name}.assigned_to_id != ? AND #{Issue.table_name}.status_id = ?", User.current.id, IssueVote::JOIN_VOTE_TYPE, User.current.id, IssueStatus.assigned.id], :order => "#{Issue.table_name}.subject ASC") @added_issues = Issue.visible.open.find(:all, :conditions => {:author_id => User.current.id}, :include => [:project, :tracker ], :order => "#{Issue.table_name}.created_at DESC") @recent_issues = User.current.recent_items(30) end # Edit user's account def account @user = User.current @pref = @user.pref if request.post? cc = params[:user][:b_cc_last_four] if cc && cc.length > 14 cc.gsub!(/[^0-9]/,'') params[:user][:b_cc_last_four] = ("XXXX-") + params[:user][:b_cc_last_four][cc.length-4,cc.length-1] if cc.length > 14 end @user.attributes = params[:user] @user.login = params[:user][:login] logger.info { "@user.attributes #{@user.attributes.inspect}" } @user.mail_notification = (params[:notification_option] == 'all') logger.info { "params[:pref] #{params[:pref].inspect}" } @user.pref.attributes = params[:pref] logger.info { "@user.pref.attributes #{@user.pref.inspect}" } logger.info { "params[:active_only_jumps] #{params[:active_only_jumps]} and boolean #{params[:active_only_jumps] == '1'}" } @user.pref[:no_self_notified] = (params[:no_self_notified] == '1') @user.pref[:daily_digest] = (params[:daily_digest] == '1') @user.pref[:no_emails] = (params[:no_emails] == '1') @user.pref[:hide_mail] = (params[:pref][:hide_mail] == '1') @user.pref[:active_only_jumps] = (params[:pref][:active_only_jumps] == '1') logger.info { "user pref #{@user.pref.inspect}" } if @user.save @user.pref.save @user.reload @user.save_billing cc, params[:ccverify], request.remote_ip @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : []) set_language_if_valid @user.language redirect_with_flash :notice, l(:notice_account_updated), :action => 'account' return end end @notification_options = [[l(:label_user_mail_option_all), 'all'], [l(:label_user_mail_option_none), 'none']] @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected') end def upgrade @user = User.current @plans = Plan.all @selected_plan = @user.plan if request.post? cc = params[:user][:b_cc_last_four] cc.gsub!(/[^0-9]/,'') logger.info { "length #{cc.length} #{cc}" } if cc.length > 14 params[:user][:b_cc_last_four] = ("XXXX-") + params[:user][:b_cc_last_four][cc.length-4,cc.length-1] else params[:user].delete :b_cc_last_four end logger.info { "inspect #{params.inspect}" } @new_plan = Plan.find(params[:user][:plan_id]) @user.attributes = params[:user] @user.plan_id = @user.plan.id #not upgrading yet account = User.update_recurly_billing @user.id, cc, params[:ccverify], request.remote_ip @user.save if defined? account.billing_info && defined? account.billing_info.errors if account.billing_info.errors.length > 0 flash.now[:error] = account.billing_info.errors[:base].collect {|v| "#{v}"}.join('
') return end end if @new_plan.code == Plan::FREE_CODE && @new_plan.code != @selected_plan.code begin sub = Recurly::Subscription.find(@user.id.to_s) sub.cancel(@user.id.to_s) rescue Exception => e flash.now[:error] = e.message return else @user.trial_expires_on = nil @user.trial_expired_at = nil @user.plan_id = @new_plan.id @user.save @user.update_usage_over @user.update_trial_expiration @user.lock_workstreams flash.now[:success] = "Your plan was successfully canceled" @user.reload return end elsif @new_plan.code != @selected_plan.code begin sub = Recurly::Subscription.find(@user.id.to_s) begin sub.change('now', :plan_code => @new_plan.code, :quantity => 1) rescue Exception => e flash.now[:error] = e.message @user.reload return end rescue ActiveResource::ResourceNotFound begin trial_expiration = @user.trial_expires_on || -1.days.from_now logger.info { "trial #{trial_expiration}" } sub = Recurly::Subscription.create( :account_code => account.account_code, :plan_code => @new_plan.code, :quantity => 1, :account => account ) rescue Exception => e flash.now[:error] = e.message @user.reload return end else @user.trial_expires_on = nil @user.trial_expired_at = nil end if sub.errors && sub.errors.any? flash.now[:error] = sub.errors.collect {|k, v| "#{v}"}.join('
') @user.reload return else @user.plan_id = @new_plan.id @user.save @user.update_usage_over @user.update_trial_expiration @user.unlock_workstreams flash.now[:success] = "Plan successfully changed to #{@new_plan.name}" end else flash.now[:success] = l(:notice_account_updated) + " No changes were made to your plan" end @user.reload return end end # Manage user's password def password @user = User.current if @user.auth_source_id flash.now[:error] = l(:notice_can_t_change_password) redirect_to :action => 'account' return end if request.post? if @user.check_password?(params[:password]) @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] if @user.save flash.now[:success] = l(:notice_account_password_updated) redirect_to :action => 'account' end else flash.now[:error] = l(:notice_account_wrong_password) end end end # Create a new feeds key def reset_rss_key if request.post? if User.current.rss_token User.current.rss_token.destroy User.current.reload end User.current.rss_key flash.now[:success] = l(:notice_feeds_access_key_reseted) end redirect_to :action => 'account' end # Create a new API key def reset_api_key if request.post? if User.current.api_token User.current.api_token.destroy User.current.reload end User.current.api_key flash.now[:success] = l(:notice_api_access_key_reseted) end redirect_to :action => 'account' end # User's page layout configuration def page_layout @user = User.current @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup @block_options = [] BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]} end # Add a block to user's page # The block is added on top of the page # params[:block] : id of the block to add def add_block block = params[:block].to_s.underscore (render :nothing => true; return) unless block && (BLOCKS.keys.include? block) @user = User.current layout = @user.pref[:my_page_layout] || {} # remove if already present in a group %w(top left right).each {|f| (layout[f] ||= []).delete block } # add it on top layout['top'].unshift block @user.pref[:my_page_layout] = layout @user.pref.save render :partial => "block", :locals => {:user => @user, :block_name => block} end # Remove a block to user's page # params[:block] : id of the block to remove def remove_block block = params[:block].to_s.underscore @user = User.current # remove block in all groups layout = @user.pref[:my_page_layout] || {} %w(top left right).each {|f| (layout[f] ||= []).delete block } @user.pref[:my_page_layout] = layout @user.pref.save render :nothing => true end # Change blocks order on user's page # params[:group] : group to order (top, left or right) # params[:list-(top|left|right)] : array of block ids of the group def order_blocks group = params[:group] @user = User.current if group.is_a?(String) group_items = (params["list-#{group}"] || []).collect(&:underscore) if group_items and group_items.is_a? Array layout = @user.pref[:my_page_layout] || {} # remove group blocks if they are presents in other groups %w(top left right).each {|f| layout[f] = (layout[f] || []) - group_items } layout[group] = group_items @user.pref[:my_page_layout] = layout @user.pref.save end end render :nothing => true end end ================================================ FILE: app/controllers/news_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class NewsController < ApplicationController default_search_scope :news before_filter :find_news, :except => [:new, :index, :preview] before_filter :find_project, :only => [:new, :preview] before_filter :authorize, :except => [:index, :preview] before_filter :find_optional_project, :only => :index accept_key_auth :index ssl_required :all log_activity_streams :current_user, :name, :announced, :@news, :title, :new, :news, {:object_description_method => :summary} log_activity_streams :current_user, :name, :edited, :@news, :title, :edit, :news, {:object_description_method => :summary} log_activity_streams :current_user, :name, :commented_on, :@news, :title, :add_comment, :news, { :object_description_method => :summary, :indirect_object => :@comment, :indirect_object_description_method => :comments, :indirect_object_phrase => '' } def index @news_pages, @newss = paginate :news, :per_page => 10, :conditions => Project.allowed_to_condition(User.current, :view_news, :project => @project), :include => [:author, :project], :order => "#{News.table_name}.created_at DESC" respond_to do |format| format.html { render :layout => false if request.xhr? } format.xml { render :xml => @newss.to_xml } format.json { render :json => @newss.to_json } end end def show @comments = @news.comments @comments.reverse! if User.current.wants_comments_in_reverse_order? end def new @news = News.new(:project => @project, :author => User.current) if request.post? @news.attributes = params[:news] if @news.save flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'news', :action => 'index', :project_id => @project end end end def edit if request.post? and @news.update_attributes(params[:news]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'show', :id => @news end end def add_comment @comment = Comment.new(params[:comment]) @comment.author = User.current if @news.comments << @comment flash.now[:success] = l(:label_comment_added) redirect_to :action => 'show', :id => @news else show render :action => 'show' end end def destroy_comment @news.comments.find(params[:comment_id]).destroy redirect_to :action => 'show', :id => @news end def destroy @news.destroy redirect_to :action => 'index', :project_id => @project end def preview @text = (params[:news] ? params[:news][:description] : nil) render :partial => 'common/preview' end private def find_news @news = News.find(params[:id]) @project = @news.project render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_optional_project return true unless params[:project_id] @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? authorize rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/notifications_controller.rb ================================================ class NotificationsController < ApplicationController ssl_required :all def index @notifications = Notification.unresponded @mentions = @notifications.select {|n| n.mention?} @notifications = @notifications.select {|n| !n.mention?} respond_to do |format| format.html format.xml { render :xml => @notifications } end end def show @notification = Notification.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @notification } end end def new @notification = Notification.new respond_to do |format| format.html format.xml { render :xml => @notification } end end def edit @notification = Notification.find(params[:id]) end def create @notification = Notification.new(params[:notification]) respond_to do |format| if @notification.save flash.now[:success] = 'Notification was successfully created.' format.html { redirect_to(@notification) } format.xml { render :xml => @notification, :status => :created, :location => @notification } else format.html { render :action => "new" } format.xml { render :xml => @notification.errors, :status => :unprocessable_entity } end end end def update @notification = Notification.find(params[:id]) respond_to do |format| if @notification.update_attributes(params[:notification]) format.html { redirect_to(@notification) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @notification.errors, :status => :unprocessable_entity } end end end def hide @notification = Notification.find(params[:notification_id]) respond_to do |format| if @notification.mark_as_responded format.js {render :action => "hide"} format.xml { head :ok } else flash.now[:success] = 'Error ignoring notification' format.js {render :action => "error"} format.xml { render :xml => @notification.errors, :status => :unprocessable_entity } end end end def destroy @notification = Notification.find(params[:id]) @notification.destroy respond_to do |format| format.html { redirect_to(notifications_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/projects_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class ProjectsController < ApplicationController menu_item :overview menu_item :activity, :only => :activity menu_item :dashboard, :only => :dashboard menu_item :files, :only => [:list_files, :add_file] menu_item :settings, :only => :settings menu_item :team, :only => :team menu_item :credits, :only => :credits ssl_required :all before_filter :find_project, :except => [ :index, :list, :copy, :activity, :update_scale, :add, :index_active, :index_latest ] before_filter :find_optional_project, :only => [:activity, :add] #BUGBUG: why aren't these actions being authorized!!! archive can be removed, unarchive doesn't seem to work when removed from here before_filter :authorize, :except => [ :index, :index_latest, :index_active, :list, :add, :copy, :archive, :unarchive, :destroy, :activity, :dashboard, :dashdata, :new_dashdata, :mypris, :update_scale, :community_members, :community_members_array, :issue_search, :hourly_types, :join] before_filter :authorize_global, :only => :add before_filter :require_admin, :only => [ :copy ] accept_key_auth :activity after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller| if controller.request.post? controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt' end end helper :sort include SortHelper helper :issues helper IssuesHelper helper :queries include QueriesHelper include ProjectsHelper log_activity_streams :current_user, :name, :edited, :@project, :name, :edit, :workstreams, {:object_description_method => :description} def index @latest_enterprises = Project.latest_public @active_enterprises = Project.most_active_public end def index_latest limit = 10 @latest_enterprises = Project.latest_public(limit, params[:offset].to_i) respond_to do |wants| wants.js do render :update do |page| page.replace "project_index_bottom_latest", :partial => "project_list", :locals => { :projects => @latest_enterprises, :offset => Integer(params[:offset]) + limit, :index_type => 'latest'} page.call "display_sparks" end end end end def index_active limit = 10 @active_enterprises = Project.most_active_public(limit, params[:offset].to_i) respond_to do |wants| wants.js do render :update do |page| page.replace "project_index_bottom_active", :partial => "project_list", :locals => { :projects => @active_enterprises, :offset => Integer(params[:offset]) + limit, :index_type => 'active'} page.call "display_sparks" end end end end def map end # Add a new project #TODO too much logic here, needs to move to model somehow def add @project = Project.new(params[:project]) @parent = Project.find(params[:parent_id]) unless params[:parent_id] == "" || params[:parent_id].nil? if request.get? @project.enabled_module_names = Setting.default_projects_modules @project.dpp = 100 if @parent @project.is_public = @parent.is_public @project.volunteer = @parent.volunteer end else @project.enabled_module_names = params[:enabled_modules] @project.is_public = params[:project][:is_public] || false @project.volunteer = params[:project][:volunteer] || false @project.homepage = url_for(:controller => 'projects', :action => 'wiki', :id => @project) if validate_parent_id && @project.save LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, :created, :workstreams, 0, nil,{:object_description_method => :description}) if @parent.nil? # Add current user as a admin and core team member r = Role.core_member r2 = Role.administrator m = Member.new(:user => User.current, :roles => [r,r2]) @project.all_members << m @project.update_attribute(:owner_id, User.current.id) else @project.set_parent!(@parent.id) @project.set_owner @project.refresh_active_members User.current.add_to_project(@project, Role.active) end flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'dashboard', :id => @project.id else redirect_with_flash :error, "Couldn't create project", :controller => "my", :action => "projects" end end end def copy @trackers = Tracker.all @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", :order => 'name') @source_project = Project.find(params[:id]) if request.get? @project = Project.copy_from(@source_project) if @project @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? else redirect_to :controller => 'admin', :action => 'projects' end else @project = Project.new(params[:project]) @project.enabled_module_names = params[:enabled_modules] if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'admin', :action => 'projects' end end rescue ActiveRecord::RecordNotFound redirect_to :controller => 'admin', :action => 'projects' end def reset_invitation_token @project.invitation_token = Token.generate_token_value @project.save respond_to do |wants| wants.js do render :update do |page| page.replace "generic-invitation", :partial => 'invitations/generic_invitation', :locals => {:project => @project} page.visual_effect :highlight, "generic-link", :duration => 6 page.visual_effect :highlight, "generic-invitation", :duration => 2 page.call '$.jGrowl', l(:notice_successful_update) end end end end def join #check token if params[:token] != @project.invitation_token render_error(l(:error_old_invite)) return else #add as contributor if @project.root? unless User.current.community_member_of? @project User.current.add_to_project @project, Role.contributor msg = "Invitation accepted. You are now a contributor of #{@project.name}" redirect_with_flash :success, msg, :controller => :projects, :action => :show, :id => @project.id else msg = "You're already on the #{@project.name} team. Invitation ignored" redirect_with_flash :error, msg, :controller => :projects, :action => :show, :id => @project.id end end end end # Show @project def overview if params[:jump] # try to redirect to the requested menu item redirect_to_project_menu_item(@project, params[:jump]) && return end @subprojects = @project.descendants.active @news = @project.news.find(:all, :conditions => " (created_at > '#{Time.now.advance :days => (Setting::DAYS_FOR_LATEST_NEWS * -1)}')", :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_at DESC") @trackers = @project.rolled_up_trackers cond = @project.project_condition(Setting.display_subprojects_issues?) @open_issues_by_tracker = Issue.visible.count(:group => :tracker, :include => [:project, :status, :tracker], :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false]) @total_issues_by_tracker = Issue.visible.count(:group => :tracker, :include => [:project, :status, :tracker], :conditions => cond) @key = User.current.rss_key @motions = @project.motions.viewable_by(User.current.position_for(@project)).allactive end def hourly_types render :json => @project.hourly_types.inject({}) { |hash, hourly_type| hash[hourly_type.id] = hourly_type.name hash }.to_json end def community_members render :json => @project.root.all_members.inject({}) { |hash, member| hash[member.user_id] = member.name hash }.to_json end def community_members_array array = [] @project.root.member_users.each {|m| array.push({:label => "#{m.user.name} (@#{m.user.login})", :value => m.user.login, :mail_hash => m.user.mail_hash })} @project.member_users.each {|m| array.push({:label => "#{m.user.name} (@#{m.user.login})", :value => m.user.login, :mail_hash => m.user.mail_hash }) } render :json => array.sort{|x,y| x[:label] <=> y[:label]}.uniq.to_json end def issue_search term = params[:searchTerm] render :json => Issue.find(:all, :conditions => "project_id = #{@project.id} AND (subject ilike '%#{term}%' OR CAST(id as varchar) ilike '%#{term}%')").to_json(:only => [:id, :subject, :description]) end def all_tags render :json => @project.all_tags(params[:term]).to_json end def dashboard @credit_base = @project.dpp @show_issue_id = params[:show_issue_id] #Optional parameter to start the dashboard off showing an issue @show_retro_id = params[:show_retro_id] #Optional parameter to start the dashboard off showing a retrospective end #TODO: optimize this query, it's WAY too heavy, and we need fewer columns, and it's executing hundreds of queries! def dashdata if params[:include_subworkstreams] project_ids = [@project.sub_project_array_visible_to(User.current).join(",")] else project_ids = [@project.id] end if params[:status_ids] conditions = "project_id in (#{project_ids}) AND (retro_id < 0 OR retro_id is null) AND status_id in (#{params[:status_ids]})" else conditions = "project_id in (#{project_ids}) AND (retro_id < 0 OR retro_id is null)" end render :json => Issue.find(:all, :conditions => conditions) \ .to_json(:include => { :journals => { :only => [:id, :notes, :created_at, :user_id], :include => {:user => { :only => [:firstname, :lastname, :login] }}}, :issue_votes => { :include => {:user => { :only => [:firstname, :lastname, :login] }}}, :status => { :only => :name }, :attachments => { :only => [:id, :filename]}, :todos => { :only => [:id, :subject, :completed_on, :owner_login] }, :tracker => { :only => [:name,:id] }, :author => { :only => [:firstname, :lastname, :login, :mail_hash] }, :assigned_to => { :only => [:firstname, :lastname, :login] } }, :except => :tags) end # Checks to see if any items have changed in this project (in the last params[:seconds]). # If it has, returns only items that have changed def new_dashdata if @project.last_item_updated_on.nil? @project.last_item_updated_on = DateTime.now @project.save end if params[:include_subworkstreams] if @project.last_item_sub_updated_on.nil? @project.last_item_sub_updated_on = DateTime.now @project.save end project_ids = [@project.sub_project_array_visible_to(User.current).join(",")] total_count = (@project.issue_count + @project.issue_count_sub).to_s last_update = @project.last_item_sub_updated_on else project_ids = [@project.id] total_count = @project.issue_count.to_s last_update = @project.last_item_updated_on end seconds_ago = params[:seconds].to_f.round if (last_update.advance(:seconds => seconds_ago) > DateTime.now) conditions = "project_id in (#{project_ids}) AND " + "updated_at >= '#{@project.last_item_updated_on.advance(:seconds => -1 * seconds_ago)}'" render :json => Issue.find(:all, :conditions => conditions).to_json( :include => { :journals => { :only => [:id, :notes, :created_at, :user_id], :include => { :user => { :only => [:firstname, :lastname, :login] } } }, :issue_votes => { :include => {:user => { :only => [:firstname, :lastname, :login] }}}, :status => { :only => :name }, :attachments => { :only => [:id, :filename]}, :todos => { :only => [:id, :subject, :completed_on, :owner_login] }, :tracker => { :only => [:name,:id] }, :author => { :only => [:firstname, :lastname, :login, :mail_hash] }, :assigned_to => { :only => [:firstname, :lastname, :login] } }, :except => :tags ) elsif params[:issuecount] != total_count render :json => Issue.find(:all, :conditions => "project_id in (#{project_ids}) AND (retro_id < 0 OR retro_id is null)" ).collect {|i| i.id} else render :text => 'no' end end def update_scale render :update do |page| page.replace 'point_scale', :partial => 'point_scale', :locals => {:dpp => params[:dpp] } page["point_scale"].visual_effect :highlight end end #TODO: remove this function, we're no longer using it?? #Returns my priorities for issues belonging to this project def mypris render :json => Issue.find(:all, :conditions => "project_id = #{@project.id} AND id IN (SELECT DISTINCT issue_id FROM #{Pri.table_name} where user_id = #{User.current.id})", :select => "id").to_json end def settings @member ||= @project.all_members.new @trackers = Tracker.all @wiki ||= @project.wiki @allow_logo_selection = true end # Edit @project def edit if request.post? old_attributes = @project.attributes @project.attributes = params[:project] @project.is_public = params[:project][:is_public] || false @project.volunteer = params[:project][:volunteer] || false if (old_attributes["is_public"] != (params[:project]["is_public"] == "1")) description = (params[:project]["is_public"] == "1") ? "publicised" : "privatized" LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, description, :workstreams, 0, nil,{}) end if validate_parent_id && @project.save @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.refresh_active_members flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project else settings render :action => 'settings' end end end def modules @project.enabled_module_names = params[:enabled_modules] @project.attributes = params[:project] @project.save redirect_with_flash :notice, l(:notice_successful_update), :action => 'settings', :id => @project, :tab => 'modules' end def archive if @project.active? && request.post? && @project.archive project_id_override = @project.parent ? @project.parent.id : @project.id #archived projects don't show up in activity stream, so we log the activity to its parent if it exists LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, l(:label_archived), :workstreams, 0, nil,{:project_id => project_id_override}) redirect_with_flash :notice, l(:notice_successful_update), :controller => "my", :action => 'projects' else render_error(l(:error_general)) end end def unarchive if !@project.active? && request.post? && @project.unarchive LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, l(:label_unarchived), :workstreams, 0, nil,{}) respond_to do |wants| wants.js do @my_projects = User.current.owned_projects render :update do |page| page.replace params[:table_id], :partial => 'my/my_projects', :locals => {:my_projects => @my_projects, :table_id => params[:table_id]} page.call '$.jGrowl', l(:notice_successful_update) page.call 'display_sparks' end end end else respond_to do |wants| wants.js do render :update do |page| page.call '$.jGrowl', l(:error_general) end end end end end # Delete @project def destroy @project_to_destroy = @project if request.post? project_id_override = @project.parent ? @project.parent.id : @project.id #deleted projects don't show up in activity stream, so we log the activity to its parent if it exists LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, l(:label_deleted), :workstreams, 0, nil,{:project_id => project_id_override}) if @project_to_destroy.destroy redirect_with_flash :notice, l(:notice_successful_delete), :controller => "welcome", :action => 'index' else render_error(l(:error_general)) end end # hide project in layout @project = nil end #move project def move redirect_to(:action => 'settings') and return if @project.root? #TODO @project.allowed_parents should be used but it isn't working correctly currently @allowed_projects = [] disallowed_projects = @project.self_and_descendants @project.root.self_and_descendants.each {|p| @allowed_projects << p if p.visible_to(User.current) && !disallowed_projects.include?(p) } if request.post? if (parent = Project.find params[:parent_id]) && @allowed_projects.include?(parent) @project.move_to_child_of(parent) flash[:success] = l(:notice_successful_update) LogActivityStreams.write_single_activity_stream(User.current, :name, @project, :name, l(:label_moved), :workstreams, 0, nil,{:project_id => @project.id}) redirect_to @project else render_403 and return end end end def add_file if request.post? container = @project attachments = attach_files(container, params[:attachments]) if !attachments.empty? && Setting.notified_events.include?('file_added') Mailer.deliver_attachments_added(attachments) end redirect_to :controller => 'projects', :action => 'list_files', :id => @project return end end def list_files sort_init 'filename', 'asc' sort_update 'filename' => "#{Attachment.table_name}.filename", 'created_at' => "#{Attachment.table_name}.created_at", 'size' => "#{Attachment.table_name}.filesize", 'downloads' => "#{Attachment.table_name}.downloads" @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] render :layout => !request.xhr? end def team @days = Setting.activity_days_default.to_i @hide_view_team_link = true #hides the link to this page from the active box end def credits @credits = @project.fetch_credits(params[:with_subprojects]) @credits_pages, @creditss = @project.fetch_credits(params[:with_subprojects]) @active_credits = @credits.find_all{|credit| credit.enabled == true && credit.settled_on.nil? == true }.group_by{|credit| credit.owner_id} @oustanding_credits = @credits.find_all{|credit| credit.settled_on.nil? == true }.group_by{|credit| credit.owner_id} @total_credits = @credits.group_by{|credit| credit.owner_id} end #params that can be passed: length, with_subprojects, and author def activity rescue ActiveRecord::RecordNotFound render_404 end private def find_project if (params[:show_issue_id]) @project = Issue.find(params[:show_issue_id]).project else @project = Project.find(params[:id]) end render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end def find_optional_project return true unless params[:id] @project = Project.find(params[:id]) authorize rescue ActiveRecord::RecordNotFound render_404 end def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) if ids = params[:tracker_ids] @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } else @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } end end # Validates parent_id param according to user's permissions # TODO: move it to Project model in a validation that depends on User.current def validate_parent_id return true if User.current.admin? parent_id = params[:project] && params[:project][:parent_id] if parent_id || @project.new_record? parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i) unless @project.allowed_parents.include?(parent) @project.errors.add :parent_id, :invalid return false end end true end end ================================================ FILE: app/controllers/queries_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class QueriesController < ApplicationController menu_item :issues before_filter :find_query, :except => :new before_filter :find_optional_project, :only => :new def new @query = Query.new(params[:query]) @query.project = params[:query_is_for_all] ? nil : @project @query.user = User.current @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.column_names = nil if params[:default_columns] params[:fields].each do |field| @query.add_filter(field, params[:operators][field], params[:values][field]) end if params[:fields] @query.group_by ||= params[:group_by] if request.post? && params[:confirm] && @query.save flash.now[:success] = l(:notice_successful_create) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query return end render :layout => false if request.xhr? end def edit if request.post? @query.filters = {} params[:fields].each do |field| @query.add_filter(field, params[:operators][field], params[:values][field]) end if params[:fields] @query.attributes = params[:query] @query.project = nil if params[:query_is_for_all] @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.column_names = nil if params[:default_columns] if @query.save flash.now[:success] = l(:notice_successful_update) redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query end end end def destroy @query.destroy if request.post? redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 end private def find_query @query = Query.find(params[:id]) @project = @query.project render_403 unless @query.editable_by?(User.current) rescue ActiveRecord::RecordNotFound render_404 end def find_optional_project @project = Project.find(params[:project_id]) if params[:project_id] User.current.allowed_to?(:save_queries, @project, :global => true) rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/quotes_controller.rb ================================================ class QuotesController < ApplicationController ssl_required :all def index @quotes = Quote.all respond_to do |format| format.html format.xml { render :xml => @quotes } end end def show @quote = Quote.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @quote } end end def new @quote = Quote.new respond_to do |format| format.html format.xml { render :xml => @quote } end end def edit @quote = Quote.find(params[:id]) end def create @quote = Quote.new(params[:quote]) @quote.user_id = User.current.id respond_to do |format| if @quote.save flash.now[:success] = 'Quote was successfully created.' format.html { redirect_to(@quote) } format.xml { render :xml => @quote, :status => :created, :location => @quote } else format.html { render :action => "new" } format.xml { render :xml => @quote.errors, :status => :unprocessable_entity } end end end def update @quote = Quote.find(params[:id]) respond_to do |format| if @quote.update_attributes(params[:quote]) flash.now[:success] = 'Quote was successfully updated.' format.html { redirect_to(@quote) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @quote.errors, :status => :unprocessable_entity } end end end def destroy @quote = Quote.find(params[:id]) @quote.destroy respond_to do |format| format.html { redirect_to(quotes_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/recurly_notifications_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class RecurlyNotificationsController < ApplicationController def listen logger.info { "params #{params.inspect}" } if params[:updated_subscription_notification] update_subscription(params[:updated_subscription_notification],true) end if params[:new_subscription_notification] update_subscription(params[:new_subscription_notification],true) end if params[:expired_subscription_notification] update_subscription(params[:expired_subscription_notification],false) end render :nothing => true end private def update_subscription(params,active_subscription) account = params["account"] subscription = params["subscription"] begin user = User.find(account["account_code"]) if active_subscription user.plan_id = Plan.find_by_code(subscription["plan"]["plan_code"]).id user.active_subscription = true else user.plan_id = Plan.find(Plan::FREE_CODE).id user.active_subscription = false end user.save logger.info { "saved for user #{user.name} new plan #{user.plan_id}" } rescue Exception => e logger.info { e.inspect } end end end ================================================ FILE: app/controllers/reports_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class ReportsController < ApplicationController menu_item :issues before_filter :find_project, :authorize def issue_report @statuses = IssueStatus.find(:all, :order => 'position') case params[:detail] when "tracker" @field = "tracker_id" @rows = @project.trackers @data = issues_by_tracker @report_title = l(:field_tracker) render :template => "reports/issue_report_details" when "assigned_to" @field = "assigned_to_id" @rows = @project.all_members.collect { |m| m.user } @data = issues_by_assigned_to @report_title = l(:field_assigned_to) render :template => "reports/issue_report_details" when "author" @field = "author_id" @rows = @project.all_members.collect { |m| m.user } @data = issues_by_author @report_title = l(:field_author) render :template => "reports/issue_report_details" when "subproject" @field = "project_id" @rows = @project.descendants.active @data = issues_by_subproject @report_title = l(:field_subproject) render :template => "reports/issue_report_details" else @trackers = @project.trackers @categories = @project.issue_categories @assignees = @project.all_members.collect { |m| m.user } @authors = @project.all_members.collect { |m| m.user } @subprojects = @project.descendants.active issues_by_tracker issues_by_assigned_to issues_by_author issues_by_subproject render :template => "reports/issue_report" end end private # Find project of id params[:id] def find_project @project = Project.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end def issues_by_tracker @issues_by_tracker ||= ActiveRecord::Base.connection.select_all("select s.id as status_id, s.is_closed as closed, t.id as tracker_id, count(i.id) as total from #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t where i.status_id=s.id and i.tracker_id=t.id and i.project_id=#{@project.id} group by s.id, s.is_closed, t.id") end def issues_by_assigned_to @issues_by_assigned_to ||= ActiveRecord::Base.connection.select_all("select s.id as status_id, s.is_closed as closed, a.id as assigned_to_id, count(i.id) as total from #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a where i.status_id=s.id and i.assigned_to_id=a.id and i.project_id=#{@project.id} group by s.id, s.is_closed, a.id") end def issues_by_author @issues_by_author ||= ActiveRecord::Base.connection.select_all("select s.id as status_id, s.is_closed as closed, a.id as author_id, count(i.id) as total from #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a where i.status_id=s.id and i.author_id=a.id and i.project_id=#{@project.id} group by s.id, s.is_closed, a.id") end def issues_by_subproject @issues_by_subproject ||= ActiveRecord::Base.connection.select_all("select s.id as status_id, s.is_closed as closed, i.project_id as project_id, count(i.id) as total from #{Issue.table_name} i, #{IssueStatus.table_name} s where i.status_id=s.id and i.project_id IN (#{@project.descendants.active.collect{|p| p.id}.join(',')}) group by s.id, s.is_closed, i.project_id") if @project.descendants.active.any? @issues_by_subproject ||= [] end end ================================================ FILE: app/controllers/reputations_controller.rb ================================================ class ReputationsController < ApplicationController ssl_required :all def index @reputations = Reputation.all respond_to do |format| format.html format.xml { render :xml => @reputations } end end def show @reputation = Reputation.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @reputation } end end def new @reputation = Reputation.new respond_to do |format| format.html format.xml { render :xml => @reputation } end end def edit @reputation = Reputation.find(params[:id]) end def create @reputation = Reputation.new(params[:reputation]) respond_to do |format| if @reputation.save flash.now[:success] = 'Reputation was successfully created.' format.html { redirect_to(@reputation) } format.xml { render :xml => @reputation, :status => :created, :location => @reputation } else format.html { render :action => "new" } format.xml { render :xml => @reputation.errors, :status => :unprocessable_entity } end end end def update @reputation = Reputation.find(params[:id]) respond_to do |format| if @reputation.update_attributes(params[:reputation]) flash.now[:success] = 'Reputation was successfully updated.' format.html { redirect_to(@reputation) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @reputation.errors, :status => :unprocessable_entity } end end end def destroy @reputation = Reputation.find(params[:id]) @reputation.destroy respond_to do |format| format.html { redirect_to(reputations_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/retro_ratings_controller.rb ================================================ class RetroRatingsController < ApplicationController ssl_required :all def index @retro_ratings = RetroRating.all respond_to do |format| format.html format.xml { render :xml => @retro_ratings } end end def show @retro_rating = RetroRating.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @retro_rating } end end def new @retro_rating = RetroRating.new respond_to do |format| format.html format.xml { render :xml => @retro_rating } end end def edit @retro_rating = RetroRating.find(params[:id]) end def create @retro_ratings = params[:retro_ratings].values.collect { |retro_rating| RetroRating.new(retro_rating) } #Archive notification for this retrospective @retro_id = params[:retro_ratings].values[0]["retro_id"] @rater_id = params[:retro_ratings].values[0]["rater_id"] Notification.update_all "state = #{Notification::STATE_ARCHIVED}" , ["variation = 'retro_started' AND source_id = ? AND recipient_id = ?", @retro_id, @rater_id] #TODO: security: make sure to only create ratings if current user is same as rater_id (and user is actually on those teams!) respond_to do |format| if @retro_ratings.all?(&:valid?) RetroRating.delete_all(:rater_id => @retro_ratings[0].rater_id , :retro_id => @retro_ratings[0].retro_id) @retro_ratings.each(&:save!) format.html { redirect_to(@retro_rating) } format.xml { render :xml => @retro_rating, :status => :created, :location => @retro_rating } format.js { render :json => @retro_ratings.to_json} else format.html { render :action => "new" } format.xml { render :xml => @retro_rating.errors, :status => :unprocessable_entity } end end end def update @retro_rating = RetroRating.find(params[:id]) respond_to do |format| if @retro_rating.update_attributes(params[:retro_rating]) flash.now[:success] = 'RetroRating was successfully updated.' format.html { redirect_to(@retro_rating) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @retro_rating.errors, :status => :unprocessable_entity } end end end def destroy @retro_rating = RetroRating.find(params[:id]) @retro_rating.destroy respond_to do |format| format.html { redirect_to(retro_ratings_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/retros_controller.rb ================================================ class RetrosController < ApplicationController before_filter :find_retro, :only => [:show] before_filter :find_project, :only => [:index, :index_json, :dashdata, :new, :edit, :create, :update, :destroy, :show_multiple] before_filter :authorize ssl_required :all def index @retros = Retro.all respond_to do |format| format.html format.xml { render :xml => @retros } end end def index_json render :json => Retro.find(:all, :conditions => {:project_id => @project.id}).to_json end def dashdata render :json => Issue.find(:all, :conditions => {:retro_id => params[:id]}).to_json(:include => {:journals => {:include => :user}, :issue_votes => {:include => :user}, :status => {:only => :name}, :todos => {:only => [:id, :subject, :completed_on]}, :tracker => {:only => [:name,:id]}, :author => {:only => [:firstname, :lastname, :login]}, :assigned_to => {:only => [:firstname, :lastname, :login]}}) end def show @retro = Retro.find(params[:id]) @team_hash = {} @final_hash = {} @user_retro_hash = {} new_user_retro = {"issues" => [], "total_points" => 0, "percentage_points" => 0, "given_percentage" => 0, "self_bias" => nil, "scale_bias" => nil, "journals" => [], "total_journals" => 0, "votes" => [], "total_votes" => 0, "total_ideas" => 0 } #Calculating oustanding points for entire retrospective @total_points = 0 @total_ideas = @retro.issues.length @max_range = 0 @pie_data_points = [] @pie_labels_points = [] @max_points = 0 #Calculating team size for issues (to distribute total points amongst team) issue_team_sizes = Hash.new @retro.issues.each {|issue| issue_team_sizes[issue.id] = 1} @retro.issues.each do |issue| issue.issue_votes.each do |issue_vote| next if issue_vote.vote_type != IssueVote::JOIN_VOTE_TYPE #Incrementing team size for the issue issue_team_sizes[issue_vote.issue_id] += 1 if issue_vote.user_id != issue.assigned_to_id end end #Adding users that have issues assigned to them and calculating total points for each user issue_group = @retro.issues.group_by{|issue| issue.assigned_to_id} issue_group.each_value {|issues| @total_points += issues.collect(&:points).sum } issue_group.keys.sort.each do |assigned_to_id| next if (@user_retro_hash.has_key? assigned_to_id) || assigned_to_id == User.sysadmin.id @user_retro_hash.store assigned_to_id, new_user_retro.dup @user_retro_hash[assigned_to_id].store "issues", issue_group[assigned_to_id] @user_retro_hash[assigned_to_id].store "total_points", 0 end #Adding users that have joined the issues and calculating total points for each user @retro.issues.each do |issue| points = issue.points.to_f / (issue_team_sizes[issue.id]) next if issue.assigned_to_id == User.sysadmin.id @user_retro_hash[issue.assigned_to_id]["total_points"]+=points @max_points = @user_retro_hash[issue.assigned_to_id]["total_points"] if @user_retro_hash[issue.assigned_to_id]["total_points"] > @max_points issue.issue_votes.each do |issue_vote| next if issue_vote.vote_type != IssueVote::JOIN_VOTE_TYPE || issue_vote.user_id == User.sysadmin.id if @user_retro_hash.has_key? issue_vote.user_id @user_retro_hash[issue_vote.user_id]["total_points"]+=points if issue_vote.user_id != issue.assigned_to_id else @user_retro_hash.store issue_vote.user_id, new_user_retro.dup @user_retro_hash[issue_vote.user_id].store "issues", [] @user_retro_hash[issue_vote.user_id].store "total_points", points end @max_points = @user_retro_hash[issue_vote.user_id]["total_points"] if @user_retro_hash[issue_vote.user_id]["total_points"] > @max_points end end @user_retro_hash.delete(nil) @user_retro_hash.keys.each do |key| @user_retro_hash[key].store "percentage_points", @total_points == 0 ? 100 : (@user_retro_hash[key]["total_points"].to_f / @total_points * 100).round_to(0).to_i @pie_data_points << @user_retro_hash[key]["percentage_points"] @pie_labels_points << User.find(key).firstname + " #{@user_retro_hash[key]["percentage_points"].to_s}%" end @max_ideas = 0 #Adding users that have authored issues and calculating total ideas generated per user author_group = @retro.issues.group_by{|issue| issue.author_id} author_group.keys.sort.each do |author_id| next if author_id == User.sysadmin.id if !(@user_retro_hash.has_key? author_id) @user_retro_hash.store author_id, new_user_retro.dup @user_retro_hash[author_id].store "issues", [] @user_retro_hash[author_id].store "total_points", 0 @user_retro_hash[author_id].store "percentage_points", 0 end @user_retro_hash[author_id].store "total_ideas", author_group[author_id].length @max_ideas = @user_retro_hash[author_id]["total_ideas"] if @user_retro_hash[author_id]["total_ideas"] > @max_ideas end @max_range = @max_ideas if @max_ideas > @max_range #Adding users that have authored journals @retro.journals.each do |journal| next if (@user_retro_hash.has_key? journal.user_id) || journal.user_id == User.sysadmin.id @user_retro_hash.store journal.user_id, new_user_retro.dup @user_retro_hash[journal.user_id].store "issues", [] @user_retro_hash[journal.user_id].store "total_points", 0 @user_retro_hash[journal.user_id].store "percentage_points", 0 end @confidence_percentage = 100 @retro.retro_ratings.each do |retro_rating| next if retro_rating.ratee_id == User.sysadmin.id || retro_rating.rater_id == User.sysadmin.id @user_retro_hash.store retro_rating.ratee_id, new_user_retro.dup unless @user_retro_hash.has_key? retro_rating.ratee_id @user_retro_hash[retro_rating.ratee_id].store "given_percentage", retro_rating.score.round if retro_rating.rater_id == User.current.id @confidence_percentage = retro_rating.confidence if retro_rating.rater_id == User.current.id @team_hash[retro_rating.ratee_id] = retro_rating.score.round if retro_rating.rater_id == RetroRating::TEAM_AVERAGE @final_hash[retro_rating.ratee_id] = retro_rating.score.round_to(0) if retro_rating.rater_id == RetroRating::FINAL_AVERAGE @user_retro_hash[retro_rating.ratee_id].store "self_bias", retro_rating.score.round if retro_rating.rater_id == RetroRating::SELF_BIAS @user_retro_hash[retro_rating.ratee_id].store "scale_bias", retro_rating.score.round if retro_rating.rater_id == RetroRating::SCALE_BIAS end #Total journals @total_journals = @retro.journals.length @pie_data_journals = [] @pie_labels_journals = [] @max_journals = 0 journals_group = @retro.journals.group_by{|journal| journal.user_id} journals_group.keys.sort.each do |user_id| next if user_id == User.sysadmin.id @user_retro_hash.store user_id, new_user_retro.dup unless @user_retro_hash.has_key? user_id @user_retro_hash[user_id].store "journals", journals_group[user_id] @user_retro_hash[user_id].store "total_journals", journals_group[user_id].length @user_retro_hash[user_id].store "percentage_journals", (@user_retro_hash[user_id]["total_journals"].to_f / @total_journals * 100).round_to(0).to_i @max_journals = @user_retro_hash[user_id]["total_journals"] if @user_retro_hash[user_id]["total_journals"] > @max_journals @pie_data_journals << @user_retro_hash[user_id]["percentage_journals"] @pie_labels_journals << User.find(user_id).firstname + " #{@user_retro_hash[user_id]["percentage_journals"].to_s}%" end @max_range = @max_journals if @max_journals > @max_range #Total voting activity @total_votes = @retro.issue_votes.length @pie_data_votes = [] @pie_labels_votes = [] @max_votes = 0 votes_group = @retro.issue_votes.group_by{|issue_vote| issue_vote.user_id} votes_group.keys.sort.each do |user_id| next if user_id == User.sysadmin.id @user_retro_hash.store user_id, new_user_retro.dup unless @user_retro_hash.has_key? user_id @user_retro_hash[user_id].store "votes", votes_group[user_id] @user_retro_hash[user_id].store "total_votes", votes_group[user_id].length @user_retro_hash[user_id].store "percentage_votes", (@user_retro_hash[user_id]["total_votes"].to_f / @total_votes * 100).round_to(0).to_i @max_votes = @user_retro_hash[user_id]["total_votes"] if @user_retro_hash[user_id]["total_votes"] > @max_votes @pie_data_votes << @user_retro_hash[user_id]["percentage_votes"] @pie_labels_votes << User.find(user_id).firstname + " #{@user_retro_hash[user_id]["percentage_votes"].to_s}%" end #Total ideas @total_ideas = @retro.issues.length @pie_data_ideas = [] @pie_labels_ideas = [] author_group.keys.sort.each do |author_id| next if author_id == User.sysadmin.id percentage = (@user_retro_hash[author_id]["total_ideas"].to_f / @total_ideas * 100).round_to(0).to_i @pie_data_ideas << percentage @pie_labels_ideas << User.find(author_id).firstname + " #{percentage.to_s}%" end @max_range = @max_votes if @max_votes > @max_range #Build Chart @point_totals = [] @vote_totals = [] @journal_totals = [] @idea_totals = [] @axis_labels = [] x_axis = '' @user_retro_hash.keys.sort.each do |user_id| @point_totals << @user_retro_hash[user_id]["total_points"] @vote_totals << @user_retro_hash[user_id]["total_votes"] @journal_totals << @user_retro_hash[user_id]["total_journals"] @idea_totals << @user_retro_hash[user_id]["total_ideas"] x_axis = x_axis + User.find(user_id).firstname + '|' end @axis_labels << x_axis #Average time taken to complete a point? respond_to do |format| format.html { render :layout => 'blank'}# show.html.erb format.xml { render :xml => @retro } end end def new @retro = Retro.new respond_to do |format| format.html format.xml { render :xml => @retro } end end def edit @retro = Retro.find(params[:id]) end def create @retro = Retro.new(params[:retro]) respond_to do |format| if @retro.save flash.now[:success] = 'Retro was successfully created.' format.html { redirect_to(@retro) } format.xml { render :xml => @retro, :status => :created, :location => @retro } else format.html { render :action => "new" } format.xml { render :xml => @retro.errors, :status => :unprocessable_entity } end end end def update @retro = Retro.find(params[:id]) respond_to do |format| if @retro.update_attributes(params[:retro]) flash.now[:success] = 'Retro was successfully updated.' format.html { redirect_to(@retro) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @retro.errors, :status => :unprocessable_entity } end end end def destroy @retro = Retro.find(params[:id]) @retro.destroy respond_to do |format| format.html { redirect_to(retros_url) } format.xml { head :ok } end end private def find_retro @retro = Retro.find(params[:id]) @project = @retro.project render_message l(:text_project_locked) if @project.locked? end def find_project @project = Project.find(params[:project_id]) render_message l(:text_project_locked) if @project.locked? end end ================================================ FILE: app/controllers/roles_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class RolesController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list } def index list render :action => 'list' unless request.xhr? end def list @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position' render :action => "list", :layout => false if request.xhr? end def new # Prefills the form with 'Non member' role permissions @role = Role.new(params[:role] || {:permissions => Role.non_member.permissions}) if request.post? && @role.save # workflow copy if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from])) @role.workflows.copy(copy_from) end flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'index' end @permissions = @role.setable_permissions @roles = Role.find :all, :order => 'builtin, position' end def edit @role = Role.find(params[:id]) if request.post? and @role.update_attributes(params[:role]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'index' end @permissions = @role.setable_permissions end def destroy @role = Role.find(params[:id]) @role.destroy redirect_to :action => 'index' rescue flash.now[:error] = 'This role is in use and can not be deleted.' redirect_to :action => 'index' end def report @roles = Role.find(:all, :order => 'builtin, position') @permissions = Redmine::AccessControl.permissions.select { |p| !p.public? } if request.post? @roles.each do |role| role.permissions = params[:permissions][role.id.to_s] role.save end flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'index' end end end ================================================ FILE: app/controllers/search_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class SearchController < ApplicationController before_filter :find_optional_project ssl_required :all helper :messages include MessagesHelper def index @question = params[:q] || "" @question.strip! @all_words = params[:all_words] || (params[:submit] ? false : true) @titles_only = !params[:titles_only].nil? projects_to_search = case params[:scope] when 'all' nil when 'my_projects' User.current.memberships.collect(&:project) when 'subprojects' @project ? (@project.self_and_descendants.active) : nil else @project end offset = nil begin; offset = params[:offset].to_time if params[:offset]; rescue; end # quick jump to an issue if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1) redirect_to :controller => "issues", :action => "show", :id => $1 return end @object_types = %w(issues news documents wiki_pages messages projects) if projects_to_search.is_a? Project # don't search projects @object_types.delete('projects') # only show what the user is allowed to view @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)} end @scope = @object_types.select {|t| params[t]} @scope = @object_types if @scope.empty? # extract tokens from the question # eg. hello "bye bye" => ["hello", "bye bye"] @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} # tokens must be at least 3 character long @tokens = @tokens.uniq.select {|w| w.length > 2 } if !@tokens.empty? # no more than 5 tokens to search for @tokens.slice! 5..-1 if @tokens.size > 5 # strings used in sql like statement like_tokens = @tokens.collect {|w| "%#{w.downcase}%"} @results = [] @results_by_type = Hash.new {|h,k| h[k] = 0} limit = 10 @scope.each do |s| r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search, :all_words => @all_words, :titles_only => @titles_only, :limit => (limit+1), :offset => offset, :before => params[:previous].nil?) @results += r @results_by_type[s] += c end @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime} if params[:previous].nil? @pagination_previous_date = @results[0].event_datetime if offset && @results[0] if @results.size > limit @pagination_next_date = @results[limit-1].event_datetime @results = @results[0, limit] end else @pagination_next_date = @results[-1].event_datetime if offset && @results[-1] if @results.size > limit @pagination_previous_date = @results[-(limit)].event_datetime @results = @results[-(limit), limit] end end else @question = "" end render :layout => false if request.xhr? end private def find_optional_project return true unless params[:id] @project = Project.find(params[:id]) check_project_privacy rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/settings_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class SettingsController < ApplicationController layout 'admin' before_filter :require_admin def index edit render :action => 'edit' end def edit @notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted wiki_content_added wiki_content_updated) if request.post? && params[:settings] && params[:settings].is_a?(Hash) settings = (params[:settings] || {}).dup.symbolize_keys settings.each do |name, value| # remove blank values in array settings value.delete_if {|v| v.blank? } if value.is_a?(Array) Setting[name] = value end flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'edit', :tab => params[:tab] return end @options = {} @options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] } @deliveries = ActionMailer::Base.perform_deliveries @guessed_host_and_path = request.host_with_port.dup @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? end def plugin @plugin = Redmine::Plugin.find(params[:id]) if request.post? Setting["plugin_#{@plugin.id}"] = params[:settings] flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'plugin', :id => @plugin.id end @partial = @plugin.settings[:partial] @settings = Setting["plugin_#{@plugin.id}"] rescue Redmine::PluginNotFound render_404 end end ================================================ FILE: app/controllers/shares_controller.rb ================================================ class SharesController < ApplicationController ssl_required :all def index @shares = Share.all @project = Project.find(params[:project_id]) unless params[:project_id].nil? respond_to do |format| format.html format.xml { render :xml => @shares } end end def show @share = Share.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @share } end end def new @share = Share.new respond_to do |format| format.html format.xml { render :xml => @share } end end def edit @share = Share.find(params[:id]) end def create @share = Share.new(params[:share]) respond_to do |format| if @share.save flash.now[:success] = 'Share was successfully created.' format.html { redirect_to(@share) } format.xml { render :xml => @share, :status => :created, :location => @share } else format.html { render :action => "new" } format.xml { render :xml => @share.errors, :status => :unprocessable_entity } end end end def update @share = Share.find(params[:id]) respond_to do |format| if @share.update_attributes(params[:share]) flash.now[:success] = 'Share was successfully updated.' format.html { redirect_to(@share) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @share.errors, :status => :unprocessable_entity } end end end def destroy @share = Share.find(params[:id]) @share.destroy respond_to do |format| format.html { redirect_to(shares_url) } format.xml { head :ok } end end end ================================================ FILE: app/controllers/todos_controller.rb ================================================ class TodosController < ApplicationController before_filter :find_issue, :only => [:index, :create, :update, :destroy ] before_filter :find_project, :authorize ssl_required :all def index @todos = Todo.all respond_to do |format| format.html format.xml { render :xml => @todos } end end def show @todo = Todo.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @todo } end end def new @todo = Todo.new respond_to do |format| format.html format.xml { render :xml => @todo } end end def edit @todo = Todo.find(params[:id]) end def create @todo = Todo.new(params[:todo]) @todo.issue_id = @issue.id respond_to do |format| if @todo.save @issue.reload format.js {render :json => @issue.to_dashboard} format.html { redirect_to(@todo) } format.xml { render :xml => @todo, :status => :created, :location => @todo } else format.html { render :action => "new" } format.xml { render :xml => @todo.errors, :status => :unprocessable_entity } end end end def update @todo = Todo.find(params[:id]) respond_to do |format| if @todo.update_attributes(params[:todo]) @issue.reload format.js {render :json => @issue.to_dashboard} format.html { redirect_to(@todo) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @todo.errors, :status => :unprocessable_entity } end end end def destroy @todo = Todo.find(params[:id]) @todo.destroy respond_to do |format| format.js {render :json => @issue.to_dashboard} format.html { redirect_to(todos_url) } format.xml { head :ok } end end private def find_issue @issue = Issue.find(params[:issue_id]) end def find_project @project = @issue.project end end ================================================ FILE: app/controllers/trackers_controller.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class TrackersController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all def index list render :action => 'list' unless request.xhr? end verify :method => :post, :only => :destroy, :redirect_to => { :action => :list } def list @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' render :action => "list", :layout => false if request.xhr? end def new @tracker = Tracker.new(params[:tracker]) if request.post? and @tracker.save # workflow copy if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) @tracker.workflows.copy(copy_from) end flash.now[:success] = l(:notice_successful_create) redirect_to :action => 'list' return end @trackers = Tracker.find :all, :order => 'position' @projects = Project.find(:all) end def edit @tracker = Tracker.find(params[:id]) if request.post? and @tracker.update_attributes(params[:tracker]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'list' return end @projects = Project.find(:all) end def destroy @tracker = Tracker.find(params[:id]) unless @tracker.issues.empty? flash.now[:error] = "This tracker contains issues and can\'t be deleted." else @tracker.destroy end redirect_to :action => 'list' end end ================================================ FILE: app/controllers/users_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class UsersController < ApplicationController before_filter :require_admin, :except => [:show, :rpx_token] ssl_required :all helper :sort include SortHelper def index sort_init 'login', 'asc' sort_update %w(login firstname lastname mail admin created_at last_login_on) @status = params[:status] ? params[:status].to_i : 1 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) unless params[:name].blank? name = "%#{params[:name].strip.downcase}%" c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name] end @user_count = User.count(:conditions => c.conditions) @user_pages = Paginator.new self, @user_count, per_page_option, params['page'] @users = User.find :all,:order => sort_clause, :conditions => c.conditions, :limit => @user_pages.items_per_page, :offset => @user_pages.current.offset render :layout => !request.xhr? end def show @user = User.find(params[:id]) # show only public projects and private projects that the logged in user is also a member of @memberships = @user.memberships.select do |membership| membership.project.visible_to(User.current) end # show only public projects and private projects that the logged in user is also a member of @reputations = @user.reputations.select do |reputation| reputation.project_id == 0 || reputation.project.visible_to(User.current) end flash.now[:notice] = l(:notice_this_is_your_profie) if @user == User.current render :layout => 'gooey' rescue ActiveRecord::RecordNotFound render_404 end def add if request.get? @user = User.new(:language => Setting.default_language) else @user = User.new(params[:user]) @user.admin = params[:user][:admin] || false @user.login = params[:user][:login] @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id if @user.save Mailer.deliver_account_information(@user, params[:password]) if params[:send_information] flash.now[:success] = l(:notice_successful_create) redirect_to(params[:continue] ? {:controller => 'users', :action => 'add'} : {:controller => 'users', :action => 'edit', :id => @user}) return end end @auth_sources = AuthSource.find(:all) end def edit @user = User.find(params[:id]) if request.post? @user.admin = params[:user][:admin] if params[:user][:admin] @user.login = params[:user][:login] if params[:user][:login] @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids] @user.attributes = params[:user] # Was the account actived ? (do it before User#save clears the change) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) if @user.save if was_activated Mailer.deliver_account_activated(@user) elsif @user.active? && params[:send_information] && !params[:password].blank? && @user.auth_source_id.nil? Mailer.deliver_account_information(@user, params[:password]) end flash.now[:success] = l(:notice_successful_update) redirect_to :back end end @auth_sources = AuthSource.find(:all) @membership ||= Member.new rescue ::ActionController::RedirectBackError redirect_to :controller => 'users', :action => 'edit', :id => @user end def edit_membership @user = User.find(params[:id]) @membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user) @membership.attributes = params[:membership] @membership.save if request.post? respond_to do |format| format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships' page.visual_effect(:highlight, "member-#{@membership.id}") } } end end def destroy_membership @user = User.find(params[:id]) @membership = Member.find(params[:membership_id]) if request.post? && @membership.deletable? @membership.destroy end respond_to do |format| format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} } end end end ================================================ FILE: app/controllers/votes_controller.rb ================================================ # An example controller for "votes" that are nested resources under users. See examples/routes.rb class VotesController < ApplicationController # First, figure out our nested scope. User or issue? before_filter :find_votes_for_my_scope, :only => [:index] ssl_required :all #TODO: figure out the equivalent of login_required in redmine and fix this line before_filter :must_own_vote, :only => [:edit, :destroy, :update] before_filter :not_allowed, :only => [:edit, :update, :new] def index respond_to do |format| format.html format.xml { render :xml => @votes } end end def show @issue = Vote.find(params[:id]) respond_to do |format| format.html format.xml { render :xml => @vote } end end def new end def edit end def create # TODO: Is there a way to cast the model from :voteable_type automatically? # Depending on the type of voteable, we dig it up from a different model # See Railscast #154 for find_commentable method case params[:voteable_type] when "issue" @voteable = Issue.find(params[:issue_id]) when "journal" @voteable = Journal.find(params[:journal_id]) when "message" @voteable = Message.find(params[:message_id]) when "reply" @voteable = Message.find(params[:reply_id]) end respond_to do |format| if User.current.vote(@voteable, params[:vote]) format.js { render :action => "create", :vote => @vote, :voteable_type => params[:voteable_type] } format.html { redirect_to([@voteable.author, @voteable]) } format.xml { render :xml => @voteable, :status => :created, :location => @voteable } else format.js { render :action => "error" } format.html { render :action => "new" } format.xml { render :xml => @vote.errors, :status => :unprocessable_entity } end end end def update end def destroy @vote = Vote.find(params[:id]) @vote.destroy respond_to do |format| format.html { redirect_to(user_votes_url) } format.xml { head :ok } end end private def find_votes_for_my_scope if params[:issue_id] @votes = Vote.for_voteable(issue.find(params[:issue_id])).descending elsif params[:user_id] @votes = Vote.for_voter(User.find(params[:user_id])).descending else @votes = [] end end def must_own_vote @vote ||= Vote.find(params[:id]) @vote.user == current_user || ownership_violation end def ownership_violation respond_to do |format| flash.now[:error] = 'You cannot edit or delete votes that you do not own!' format.html do redirect_to user_path(User.current) end end end end ================================================ FILE: app/controllers/watchers_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class WatchersController < ApplicationController before_filter :find_project before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :authorize, :only => [:new, :destroy] ssl_required :all verify :method => :post, :only => [ :watch, :unwatch ], :render => { :nothing => true, :status => :method_not_allowed } def watch if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) render_403 else set_watcher(User.current, true) end end def unwatch set_watcher(User.current, false) end def new @watcher = Watcher.new(params[:watcher]) @watcher.watchable = @watched @watcher.save if request.post? respond_to do |format| format.html { redirect_to :back } format.js do render :update do |page| page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} end end end rescue ::ActionController::RedirectBackError render :text => 'Watcher added.', :layout => true end def destroy @watched.set_watcher(User.find(params[:user_id]), false) if request.post? respond_to do |format| format.html { redirect_to :back } format.js do render :update do |page| page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} end end end end private def find_project klass = Object.const_get(params[:object_type].camelcase) return false unless klass.respond_to?('watched_by') @watched = klass.find(params[:object_id]) @project = @watched.project rescue render_404 end def set_watcher(user, watching) @watched.set_watcher(user, watching) if params[:replace].present? if params[:replace].is_a? Array replace_ids = params[:replace] else replace_ids = [params[:replace]] end else replace_ids = 'watcher' end respond_to do |format| format.html { redirect_to :back } format.js do render(:update) do |page| replace_ids.each do |replace_id| page.replace_html replace_id, watcher_link(@watched, user, :replace => replace_ids) end end end end rescue ::ActionController::RedirectBackError render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true end end ================================================ FILE: app/controllers/welcome_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class WelcomeController < ApplicationController caches_action :robots ssl_required :all before_filter :require_login, :except => :robots def index @my_projects = User.current.recent_projects(10) unless @my_projects.nil? @news = News.find(:all, :limit => 30, :order => "#{News.table_name}.created_at DESC", :conditions => "#{News.table_name}.project_id in (#{User.current.projects.collect{|m| m.id}.join(',')}) AND (created_at > '#{Time.now.advance :days => (Setting::DAYS_FOR_LATEST_NEWS * -1)}')", :include => [:project, :author]) unless User.current.projects.empty? @assigned_issues = Issue.visible.open.find(:all, :conditions => ["#{IssueVote.table_name}.user_id = ? AND #{IssueVote.table_name}.vote_type = ? AND #{Issue.table_name}.status_id = ?", User.current.id, IssueVote::JOIN_VOTE_TYPE, IssueStatus.assigned.id], :include => [:project, :tracker, :issue_votes ], :order => "#{Project.table_name}.name ASC") end end def robots @projects = Project.all_public.active render :layout => false, :content_type => 'text/plain' end end ================================================ FILE: app/controllers/wiki_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'diff' class WikiController < ApplicationController default_search_scope :wiki_pages before_filter :find_wiki, :authorize before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] ssl_required :all verify :method => :post, :only => [:destroy, :protect], :redirect_to => { :action => :index } helper :attachments include AttachmentsHelper helper :watchers log_activity_streams :current_user, :name, :attached, :@page, :title, :add_attachment, :wikis, {} # display a page (in editing mode if it doesn't exist) def index page_title = params[:page] @page = @wiki.find_or_new_page(page_title) if @page.new_record? if User.current.allowed_to?(:edit_wiki_pages, @project) edit render :action => 'edit' else render_404 end return end if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project) # Redirects user to the current version if he's not allowed to view previous versions redirect_to :version => nil return end @content = @page.content_for_version(params[:version]) if params[:format] == 'html' export = render_to_string :action => 'export', :layout => false send_data(export, :type => 'text/html', :filename => "#{@page.title}.html") return elsif params[:format] == 'txt' send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt") return end @editable = editable? render :action => 'show' end # edit an existing page or a new one def edit @page = @wiki.find_or_new_page(params[:page]) return render_403 unless editable? @page.content = WikiContent.new(:page => @page) if @page.new_record? @content = @page.content_for_version(params[:version]) @content.text = initial_page_content(@page) if @content.text.blank? # don't keep previous comment @content.comments = nil if request.get? # To prevent StaleObjectError exception when reverting to a previous version @content.version = @page.content.version else if !@page.new_record? && @content.text == params[:content][:text] # don't save if text wasn't changed redirect_to :action => 'index', :id => @project, :page => @page.title return end @content.text = params[:content][:text] @content.comments = params[:content][:comments] @content.attributes = params[:content] @content.author = User.current # if page is new @page.save will also save content, but not if page isn't a new record if @page.new_record? @page.save LogActivityStreams.write_single_activity_stream(User.current, :name, @page, :title, :created, :wikis, 0, nil,{}) redirect_to :action => 'index', :id => @project, :page => @page.title else @content.save LogActivityStreams.write_single_activity_stream(User.current, :name, @page, :title, :edited, :wikis, 0, nil,{}) redirect_to :action => 'index', :id => @project, :page => @page.title end end rescue ActiveRecord::StaleObjectError # Optimistic locking exception flash.now[:error] = l(:notice_locking_conflict) end # rename a page def rename return render_403 unless editable? @page.redirect_existing_links = true # used to display the *original* title if some AR validation errors occur @original_title = @page.pretty_title if request.post? && @page.update_attributes(params[:wiki_page]) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'index', :id => @project, :page => @page.title end end def protect @page.update_attribute :protected, params[:protected] redirect_to :action => 'index', :id => @project, :page => @page.title end # show page history def history @version_count = @page.content.versions.count @version_pages = Paginator.new self, @version_count, per_page_option, params['p'] # don't load text @versions = @page.content.versions.find :all, :select => "id, author_id, comments, updated_at, version", :order => 'version DESC', :limit => @version_pages.items_per_page + 1, :offset => @version_pages.current.offset render :layout => false if request.xhr? end def diff @diff = @page.diff(params[:version], params[:version_from]) render_404 unless @diff end def annotate @annotate = @page.annotate(params[:version]) render_404 unless @annotate end # Removes a wiki page and its history # Children can be either set as root pages, removed or reassigned to another parent page def destroy return render_403 unless editable? @descendants_count = @page.descendants.size if @descendants_count > 0 case params[:todo] when 'nullify' # Nothing to do when 'destroy' # Removes all its descendants @page.descendants.each(&:destroy) when 'reassign' # Reassign children to another parent page reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i) return unless reassign_to @page.children.each do |child| child.update_attribute(:parent, reassign_to) end else @reassignable_to = @wiki.pages - @page.self_and_descendants return end end @page.destroy redirect_to :action => 'special', :id => @project, :page => 'Page_index' end # display special pages def special page_title = params[:page].downcase case page_title # show pages index, sorted by title when 'page_index', 'date_index' # eager load information about last updates, without loading text @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_at", :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id", :order => 'title' @pages_by_date = @pages.group_by {|p| p.updated_at.to_date} @pages_by_parent_id = @pages.group_by(&:parent_id) # export wiki to a single html file when 'export' @pages = @wiki.pages.find :all, :order => 'title' export = render_to_string :action => 'export_multiple', :layout => false send_data(export, :type => 'text/html', :filename => "wiki.html") return else # requested special page doesn't exist, redirect to default page redirect_to :action => 'index', :id => @project, :page => nil return end render :action => "special_#{page_title}" end def preview page = @wiki.find_page(params[:page]) # page is nil when previewing a new page return render_403 unless page.nil? || editable?(page) if page @attachements = page.attachments @previewed = page.content end @text = params[:content][:text] render :partial => 'common/preview' end def add_attachment return render_403 unless editable? attach_files(@page, params[:attachments]) redirect_to :action => 'index', :page => @page.title end private def find_wiki @project = Project.find(params[:id]) render_message l(:text_project_locked) if @project.locked? @wiki = @project.wiki render_404 unless @wiki rescue ActiveRecord::RecordNotFound render_404 end # Finds the requested page and returns a 404 error if it doesn't exist def find_existing_page @page = @wiki.find_page(params[:page]) render_404 if @page.nil? end # Returns true if the current user is allowed to edit the page, otherwise false def editable?(page = @page) page.editable_by?(User.current) end # Returns the default content of a new wiki page def initial_page_content(page) helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) extend helper unless self.instance_of?(helper) helper.instance_method(:initial_page_content).bind(self).call(page) end end ================================================ FILE: app/controllers/wikis_controller.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class WikisController < ApplicationController menu_item :settings before_filter :find_project, :authorize ssl_required :all # Create or update a project's wiki def edit @wiki = @project.wiki || Wiki.new(:project => @project) @wiki.attributes = params[:wiki] @wiki.save if request.post? render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'} end # Delete a project's wiki def destroy if request.post? && params[:confirm] && @project.wiki @project.wiki.destroy redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' end end private def find_project @project = Project.find(params[:id]) render_message l(:text_project_locked) if @project.locked? rescue ActiveRecord::RecordNotFound render_404 end end ================================================ FILE: app/controllers/workflows_controller.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # class WorkflowsController < ApplicationController layout 'admin' before_filter :require_admin ssl_required :all def index @workflow_counts = Workflow.count_by_tracker_and_role end def edit @role = Role.find_by_id(params[:role_id]) @tracker = Tracker.find_by_id(params[:tracker_id]) if request.post? Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) (params[:issue_status] || []).each { |old, news| news.each { |new| @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new) } } if @role.save flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker end end @roles = Role.find(:all, :order => 'builtin, position') @trackers = Tracker.find(:all, :order => 'position') @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) if @tracker && @used_statuses_only && @tracker.issue_statuses.any? @statuses = @tracker.issue_statuses end @statuses ||= IssueStatus.find(:all, :order => 'position') end def copy @trackers = Tracker.find(:all, :order => 'position') @roles = Role.find(:all, :order => 'builtin, position') if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' @source_tracker = nil else @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i) end if params[:source_role_id].blank? || params[:source_role_id] == 'any' @source_role = nil else @source_role = Role.find_by_id(params[:source_role_id].to_i) end @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) if request.post? if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) flash.now[:error] = l(:error_workflow_copy_source) elsif @target_trackers.nil? || @target_roles.nil? flash.now[:error] = l(:error_workflow_copy_target) else Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles) flash.now[:success] = l(:notice_successful_update) redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role end end end end ================================================ FILE: app/helpers/admin_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module AdminHelper def project_status_options_for_select(selected) options_for_select([[l(:label_all), ''], [l(:status_active), 1]], selected) end def css_project_classes(project) s = 'project' s << ' root' if project.root? s << ' child' if project.child? s << (project.leaf? ? ' leaf' : ' parent') s end end ================================================ FILE: app/helpers/application_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'coderay' require 'coderay/helpers/file_type' require 'forwardable' require 'cgi' module ApplicationHelper include Redmine::WikiFormatting::Macros::Definitions include Redmine::I18n include GravatarHelper::PublicMethods extend Forwardable def_delegators :wiki_helper def help_section(name, popup=false) if popup return if User.current.anonymous? help_section = HelpSection.first(:conditions => {:user_id => User.current.id, :name => name}) if help_section.nil? help_section = HelpSection.create( :user_id => User.current.id, :name => name, :show => true ) end render :partial => 'help_sections/show_popup', :locals => {:help_section => help_section} if help_section.show else render :partial => 'help_sections/show', :locals => {:name => name} end end # Return true if user is authorized for controller/action, otherwise false def authorize_for(controller, action) logger.info { "authorize for #{controller} #{action} #{@project.name}" } User.current.allowed_to?({:controller => controller, :action => action}, @project) end # Display a link if user is authorized def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) end # Display a link if user is not logged in def link_to_if_anon(name, options = {}, html_options = nil, *parameters_for_method_reference) link_to(name, options, html_options, *parameters_for_method_reference) if User.current == User.anonymous end # Display a link to remote if user is authorized def link_to_remote_if_authorized(name, options = {}, html_options = nil) url = options[:url] || {} link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) end # Displays a link to user's account page if active def link_to_user(user, options={}) if user.is_a?(User) name = h(user.name(options[:format])) if user.active? link_to name, :controller => 'users', :action => 'show', :id => user else name end else h(user.to_s) end end def link_to_user_or_you(user, options={}) if user == User.current "You" else link_to_user(user,options) end end # Displays a link to project def link_to_project(project, options={}) if project.is_a?(Project) name = h(project.name) link_to name, :controller => 'projects', :action => 'show', :id => project else h(project.to_s) end end def link_to_user_from_id(user_id, options={}) link_to_user(User.find(user_id)) end # Displays a link to +issue+ with its subject. # Examples: # # link_to_issue(issue) # => Defect #6: This is the subject # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... # link_to_issue(issue, :subject => false) # => Defect #6 # link_to_issue(issue, :project => true) # => Foo - Defect #6 # def link_to_issue(issue, options={}) title = nil subject = nil css_class = nil if options[:subject] == false title = truncate(issue.subject, :length => 60) else subject = issue.subject if options[:truncate] subject = truncate(subject, :length => options[:truncate]) end end if options[:css_class] css_class = options[:css_class] else css_class = issue.css_classes end css_class = css_class + " fancyframe" #loads fancybox s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, :class => css_class, :title => title s << ": #{h subject}" if subject s = "#{h issue.project} - " + s if options[:project] s end def link_to_issue_from_id(issue_id, options={}) link_to_issue(Issue.find(issue_id), options) rescue ActiveRecord::RecordNotFound css_class = "fancyframe" #loads fancybox s = link_to "Issue ##{issue_id}", {:controller => "issues", :action => "show", :id => issue_id}, :class => css_class end # Generates a link to an attachment. # Options: # * :text - Link text (default to attachment filename) # * :download - Force download (default: false) def link_to_attachment(attachment, options={}) text = options.delete(:text) || attachment.filename action = options.delete(:download) ? 'download' : 'show' link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options) end def current_user User.current end def logged_in? User.current.logged? end def toggle_link(name, id, options={}) onclick = "$('##{id}').toggle(); " onclick << "$('##{options[:second_toggle]}').toggle(); " if options[:second_toggle] onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") onclick << "return false;" link_to(name, "#", options.merge({:onclick => onclick})) end def image_to_function(name, function, html_options = {}) html_options.symbolize_keys! tag(:input, html_options.merge({ :type => "image", :src => image_path(name), :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" })) end def prompt_to_remote(name, text, param, url, html_options = {}) html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" link_to name, {}, html_options end #id is the id of the element sending the request #name is the text on the link #title of the command prompt #message bellow title in prompt #params to be passed with url #url to submit to after input is collected #required input or just optional #html_options for this link def prompt_input_to_remote(id, name, title, message, param, url, required, html_options = {}) html_options[:onclick] = "comment_prompt_to_remote('#{id}', '#{title}', '#{message}', '#{param}', '#{url_for(url)}', #{required}); return false;" link_to name, {}, html_options end def format_activity_title(text) h(truncate_single_line(text, :length => 100)) end def format_activity_day(date) date == Date.today ? l(:label_today).titleize : format_date(date) end def format_activity_description(text) make_expandable(textilizable(text),300) end def make_expandable(newhtml,length=400) return if newhtml.nil? return newhtml if newhtml.gsub(/<\/?[^>]*>/, "").length < length id = rand(100000) h = "" h << "" h << "
" h << newhtml.truncate_html(length) h = h[0..-5] h << "... see more" h << "

" h << "

" end def due_date_distance_in_words(date) if date l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) end end def render_page_hierarchy(pages, node=nil) content = '' if pages[node] content << "\n" end content end # Renders flash messages def render_flash_messages s = '' flash.each do |k,v| s << content_tag('div', v, :class => "flash #{k}") end s end def render_global_messages s = '' if User.current.logged? && User.current.trial_expired_at && User.current.trial_expired_at < (-1 * Setting::GLOBAL_OVERUSE_THRESHOLD).days.from_now s << content_tag('div', link_to(l(:text_trial_expired), {:controller => 'my', :action => 'upgrade'}), :class => "flash error") elsif User.current.logged? && User.current.usage_over_at && User.current.usage_over_at < (-1 * Setting::GLOBAL_OVERUSE_THRESHOLD).days.from_now s << content_tag('div', link_to(l(:text_usage_over), {:controller => 'my', :action => 'upgrade'}), :class => "flash error") end s end # Renders tabs and their content def render_tabs(tabs) if tabs.any? render :partial => 'common/tabs', :locals => {:tabs => tabs} else content_tag 'p', l(:label_no_data), :class => "nodata" end end # Renders the project quick-jump box def render_project_jump_box # Retrieve them now to avoid a COUNT query if User.current.pref[:active_only_jumps] projects = User.current.projects.all else project_ids = User.current.projects.collect{|p| p.id}.join(",") projects = project_ids.any? ? Project.find(:all, :conditions => "(parent_id in (#{project_ids}) OR id in (#{project_ids})) AND (status=#{Project::STATUS_ACTIVE})") : [] end s = '' s << '' end def sub_workstream_project_box(project) return '' if project.nil? @project_descendants = project.descendants.active return '' if @project_descendants.length == 0 s = '' end def project_tree_options_for_select(projects, options = {}) s = '' project_tree_sorted(projects) do |project, level| name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') tag_options = {:value => project.id, :selected => ((project == options[:selected] && false) ? 'selected' : nil)} tag_options.merge!(yield(project)) if block_given? s << content_tag('option', name_prefix + h(project), tag_options) end s end # Yields the given block for each project with its level in the tree def project_tree(projects, &block) ancestors = [] projects.sort_by(&:lft).each do |project| while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) ancestors.pop end yield project, ancestors.size ancestors << project end end def project_tree_sorted(projects, &block) ancestors = [] sorted = [] #nested array for alphabetical sorting last_array = sorted projects.sort_by(&:lft).each do |project| while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) ancestors.pop end if ancestors.size == 0 sorted << [[project.name, ancestors.size,project]] else sorted_string = "sorted" + ".last" * ancestors.size eval(sorted_string) << [[project.name, ancestors.size,project]] end # yield project, ancestors.size ancestors << project end sorted = sort2d(sorted) traverse_sorted(sorted, &block) sorted end def sort2d(ar) ar.sort! {|a,b| a[0][0][0] <=> b[0][0][0]} if ar[0][0].class.to_s != "String" ar.each {|sub| sub = sort2d(sub)} end end def traverse_sorted(ar, &block) unless ar[0].class.to_s != "String" yield ar[2], ar[1] else ar.each {|sub| sub = traverse_sorted(sub, &block)} end end def show_detail(detail, no_html=false) case detail.property when 'attr' label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym) case detail.prop_key when 'due_date', 'start_date' value = format_date(detail.value.to_date) if detail.value old_value = format_date(detail.old_value.to_date) if detail.old_value when 'project_id' p = Project.find_by_id(detail.value) and value = p.name if detail.value p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value when 'status_id' s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value when 'tracker_id' t = Tracker.find_by_id(detail.value) and value = t.name if detail.value t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value when 'assigned_to_id' u = User.find_by_id(detail.value) and value = u.name if detail.value u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value when 'estimated_hours' value = "%0.02f" % detail.value.to_f unless detail.value.blank? old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank? end when 'attachment' label = l(:label_attachment) end label ||= detail.prop_key value ||= detail.value old_value ||= detail.old_value unless no_html label = content_tag('strong', label) old_value = content_tag("i", h(old_value)) if detail.old_value old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?) if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key) # Link to the attachment if it has not been removed value = link_to_attachment(a) else value = content_tag("i", h(value)) if value end end if !detail.value.blank? case detail.property when 'attr', 'cf' if !detail.old_value.blank? l(:text_journal_changed, :label => label, :old => old_value, :new => value) else l(:text_journal_set_to, :label => label, :value => value) end when 'attachment' l(:text_journal_added, :label => label, :value => value) end else l(:text_journal_deleted, :label => label, :old => old_value) end end def format_time_ago(updated_at) "#{distance_of_time_in_words(Time.now,local_time(updated_at))} ago" end def project_nested_ul(projects, &block) s = '' if projects.any? ancestors = [] projects.sort_by(&:lft).each do |project| if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) s << "\n" end end s << "
  • " s << yield(project).to_s ancestors << project end s << ("
  • \n" * ancestors.size) end s end def users_check_box_tags(name, users) s = '' users.sort.each do |user| s << "\n" end s end # Truncates and returns the string as a single line def truncate_single_line(string, *args) truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') end def html_hours(text) text.gsub(%r{(\d+)\.(\d+)}, '\1.\2') end def authoring(created, author, options={}) l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) end def time_tag(time) text = distance_of_time_in_words(Time.now, time) if @project link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time)) else content_tag('acronym', text, :title => format_time(time)) end end def since_tag(time) text = distance_of_time_in_words(Time.now, time).gsub(/about/,"") content_tag('acronym', text, :title => format_time(time)) end def syntax_highlight(name, content) type = CodeRay::FileType[name] type ? CodeRay.scan(content, type).html : h(content) end def to_path_param(path) path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} end def pagination_links_full(paginator, count=nil, options={}) page_param = options.delete(:page_param) || :page url_param = params.dup # don't reuse query params if filters are present url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter) html = '' if paginator.current.previous html << link_to_remote_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' end html << (pagination_links_each(paginator, options) do |n| link_to_remote_content_update(n.to_s, url_param.merge(page_param => n)) end || '') if paginator.current.next html << ' ' + link_to_remote_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) end unless count.nil? html << [ " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page) ].compact.join(' | ') end html end def per_page_links(selected=nil) url_param = params.dup url_param.clear if url_param.has_key?(:set_filter) links = Setting.per_page_options_array.collect do |n| n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n), :method => :get}, {:href => url_for(url_param.merge(:per_page => n))}) end links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil end def reorder_links(name, url) link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) end def breadcrumb(*args) elements = args.flatten elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil end def other_formats_links(&block) concat('

    ' + l(:label_export_to)) yield Redmine::Views::OtherFormatsBuilder.new(self) concat('

    ') end def page_header_title if @project.nil? link_to(@page_header_name.nil? ? User.current.name : "Bettermeans", {:controller => 'welcome', :action => 'index'}) + (@page_header_name.nil? ? '' : ' » ' + @page_header_name) elsif @project.new_record? #TODO: would be nice to have the project's parent name here if it's a new record b = [] b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root') unless @parent.nil? ancestors = (@parent.root? ? [] : @parent.ancestors.visible) if ancestors.any? root = ancestors.shift b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item }, :class => 'root') if ancestors.size > 2 b << '…' ancestors = ancestors[-2, 2] end b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') } end b << link_to(h(@parent), {:controller => 'projects', :action => 'show', :id => @parent, :jump => current_menu_item}, :class => 'ancestor') b << "New sub workstream" b = b.join(' » ') b else b << l(:label_project_new) b = b.join(' » ') b end else b = [] b << link_to(l(:label_project_plural), {:controller => 'projects', :action => 'index'}, :class => 'root') ancestors = (@project.root? ? [] : @project.ancestors.visible) if ancestors.any? root = ancestors.shift b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root') if ancestors.size > 2 b << '…' ancestors = ancestors[-2, 2] end b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') } end b.push link_to(h(@project), {:controller => 'projects', :action => 'show', :id => @project, :jump => current_menu_item}, :class => 'ancestor') b = b.join(' » ') end end def page_header_name begin if @project.nil? || @project.new_record? @page_header_name.nil? ? l(:label_my_home) : @page_header_name elsif @project.new_record? l(:label_project_new) else html = h(@project.name) html << privacy(@project) html << volunteering(@project) html end rescue "Home" end end def html_title(*args) if args.empty? title = [] title << @project.name if @project title += @html_title if @html_title title << Setting.app_title title.select {|t| !t.blank? }.join(' - ') else @html_title ||= [] @html_title += args end end def accesskey(s) Redmine::AccessKeys.key_for s end # Formats text according to system settings. # 2 ways to call this method: # * with a String: textilizable(text, options) # * with an object and one of its attribute: textilizable(issue, :description, options) def textilizable(*args) options = args.last.is_a?(Hash) ? args.pop : {} case args.size when 1 obj = options[:object] text = args.shift when 2 obj = args.shift text = obj.send(args.shift).to_s else raise ArgumentError, 'invalid arguments to textilizable' end return '' if text.blank? only_path = options.delete(:only_path) == false ? false : true # when using an image link, try to use an attachment, if possible attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) if attachments attachments = attachments.sort_by(&:created_at).reverse text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m| style = $1 filename = $6.downcase # search for the picture in attachments if found = attachments.detect { |att| att.filename.downcase == filename } image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1") alt = desc.blank? ? nil : "(#{desc})" "!#{style}#{image_url}#{alt}!" else m end end end text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) } # different methods for formatting wiki links case options[:wiki_links] when :local # used for local links to html files format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" } when :anchor # used for single-file wiki export format_wiki_link = Proc.new {|project, title, anchor| "##{title}" } else format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) } end project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) # Wiki links # # Examples: # [[mypage]] # [[mypage|mytext]] # wiki links can refer other project wikis, using project name or identifier: # [[project:]] -> wiki starting page # [[project:|mytext]] # [[project:mypage]] # [[project:mypage|mytext]] text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| link_project = project esc, all, page, title = $1, $2, $3, $5 if esc.nil? if page =~ /^([^\:]+)\:(.*)$/ link_project = Project.find_by_name($1) || Project.find_by_identifier($1) page = $2 title ||= $1 if page.blank? end if link_project && link_project.wiki # extract anchor anchor = nil if page =~ /^(.+?)\#(.+)$/ page, anchor = $1, $2 end # check if page exists wiki_page = link_project.wiki.find_page(page) link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor), :class => ('wiki-page' + (wiki_page ? '' : ' new'))) else # project or wiki doesn't exist all end else all end end # Redmine links # # Examples: # Issues: # #52 -> Link to issue #52 # Documents: # document#17 -> Link to document with id 17 # document:Greetings -> Link to the document with title "Greetings" # document:"Some document" -> Link to the document with title "Some document" # Versions: # version#3 -> Link to version with id 3 # version:1.0.0 -> Link to version named "1.0.0" # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" # Attachments: # attachment:file.zip -> Link to the attachment of the current object named file.zip # Source files: # source:some/file -> Link to the file located at /some/file in the project's repository # source:some/file@52 -> Link to the file's revision 52 # source:some/file#L120 -> Link to line 120 of the file # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 # export:some/file -> Force the download of the file # Forum messages: # message#1218 -> Link to message with id 1218 # User mentions: # @userlogin -> Link to user with login:userlogin # text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m| text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(@)([a-zA-Z0-9._@]+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m| leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8 link = nil if esc.nil? if sep == '#' oid = oid.to_i case prefix when nil if issue = Issue.visible.find_by_id(oid, :include => :status) link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, :class => issue.css_classes, :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") end when 'document' if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current)) link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, :class => 'document' end when 'message' if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current)) link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path, :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :anchor => (message.parent ? "message-#{message.id}" : nil)}, :class => 'message' end end elsif sep == '@' link = link_to("@#{oid}", {:only_path => only_path, :controller => 'users', :action => 'show', :id => 0, :login => oid}) elsif sep == ':' # removes the double quotes if any name = oid.gsub(%r{^"(.*)"$}, "\\1") case prefix when 'document' if project && document = project.documents.find_by_title(name) link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, :class => 'document' end when 'attachment' if attachments && attachment = attachments.detect {|a| a.filename == name } link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment' end end end end leading + (link || "#{prefix}#{sep}#{oid}") end text end # Same as Rails' simple_format helper without using paragraphs def simple_format_without_paragraph(text) text.to_s. gsub(/\r\n?/, "\n"). # \r\n and \r -> \n gsub(/\n\n+/, "

    "). # 2+ newline -> 2 br gsub(/([^\n]\n)(?=[^\n])/, '\1
    ') # 1 newline -> br end def lang_options_for_select(blank=true) (blank ? [["(auto)", ""]] : []) + valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } end def month_hash [ ["01 - January",1], ["02 - February",2], ["03 - March",3], ["04 - April",4], ["05 - May",5], ["06 - June",6], ["07 - July",7], ["08 - August",8], ["09 - September",9], ["10 - October",10], ["11 - November",11], ["12 - December",12] ] end def privacy(project) project.is_public ? "" : help_bubble(:help_this_workstream_is_private, {:image =>"icon_privacy.png"}) end def volunteering(project) project.volunteer ? help_bubble(:help_volunteer, {:image => "icon_volunteer.png"}) : "" end def year_hash [0,1,2,3,4,5,6,7,8,9,10].collect{|n| [(Date.today.year + n).to_s, Date.today.year + n]} end def unit_for(project) if project.volunteer? return '♥' else return '●' end end def country_hash { "Afghanistan" => "AF", "Albania" => "AL", "Algeria" => "DZ", "American Samoa" => "AS", "Andorra" => "AD", "Angola" => "AO", "Anguilla" => "AI", "Antigua and Barbuda" => "AG", "Argentina" => "AR", "Armenia" => "AM", "Aruba" => "AW", "Australia" => "AU", "Austria" => "AT", "Aland Islands" => "AX", "Azerbaijan" => "AZ", "Bahamas" => "BS", "Bahrain" => "BH", "Bangladesh" => "BD", "Barbados" => "BB", "Belarus" => "BY", "Belgium" => "BE", "Belize" => "BZ", "Benin" => "BJ", "Bermuda" => "BM", "Bhutan" => "BT", "Bolivia" => "BO", "Bosnia and Herzegovina" => "BA", "Botswana" => "BW", "Bouvet Island" => "BV", "Brazil" => "BR", "Brunei Darussalam" => "BN", "British Indian Ocean Territory" => "IO", "Bulgaria" => "BG", "Burkina Faso" => "BF", "Burundi" => "BI", "Cambodia" => "KH", "Cameroon" => "CM", "Canada" => "CA", "Cape Verde" => "CV", "Cayman Islands" => "KY", "Central African Republic" => "CF", "Chad" => "TD", "Chile" => "CL", "China" => "CN", "Christmas Island" => "CX", "Cocos (Keeling) Islands" => "CC", "Colombia" => "CO", "Comoros" => "KM", "Congo" => "CG", "Congo, the Democratic Republic of the" => "CD", "Cook Islands" => "CK", "Costa Rica" => "CR", "Cote D'Ivoire" => "CI", "Croatia" => "HR", "Cuba" => "CU", "Cyprus" => "CY", "Czech Republic" => "CZ", "Denmark" => "DK", "Djibouti" => "DJ", "Dominica" => "DM", "Dominican Republic" => "DO", "Ecuador" => "EC", "Egypt" => "EG", "El Salvador" => "SV", "Equatorial Guinea" => "GQ", "Eritrea" => "ER", "Estonia" => "EE", "Ethiopia" => "ET", "Falkland Islands (Malvinas)" => "FK", "Faroe Islands" => "FO", "Fiji" => "FJ", "Finland" => "FI", "France" => "FR", "French Guiana" => "GF", "French Polynesia" => "PF", "French Southern Territories" => "TF", "Gabon" => "GA", "Gambia" => "GM", "Georgia" => "GE", "Germany" => "DE", "Ghana" => "GH", "Gibraltar" => "GI", "Greece" => "GR", "Greenland" => "GL", "Grenada" => "GD", "Guadeloupe" => "GP", "Guam" => "GU", "Guatemala" => "GT", "Guinea" => "GN", "Guinea-Bissau" => "GW", "Guyana" => "GY", "Guernsey" => "GG", "Haiti" => "HT", "Holy See (Vatican City State)" => "VA", "Honduras" => "HN", "Hong Kong" => "HK", "Heard Island And Mcdonald Islands" => "HM", "Hungary" => "HU", "Iceland" => "IS", "India" => "IN", "Indonesia" => "ID", "Iran, Islamic Republic of" => "IR", "Iraq" => "IQ", "Ireland" => "IE", "Isle Of Man" => "IM", "Israel" => "IL", "Italy" => "IT", "Jamaica" => "JM", "Japan" => "JP", "Jersey" => "JE", "Jordan" => "JO", "Kazakhstan" => "KZ", "Kenya" => "KE", "Kiribati" => "KI", "Korea, Democratic People's Republic of" => "KP", "Korea, Republic of" => "KR", "Kuwait" => "KW", "Kyrgyzstan" => "KG", "Lao People's Democratic Republic" => "LA", "Latvia" => "LV", "Lebanon" => "LB", "Lesotho" => "LS", "Liberia" => "LR", "Libyan Arab Jamahiriya" => "LY", "Liechtenstein" => "LI", "Lithuania" => "LT", "Luxembourg" => "LU", "Macao" => "MO", "Macedonia, the Former Yugoslav Republic of" => "MK", "Madagascar" => "MG", "Malawi" => "MW", "Malaysia" => "MY", "Maldives" => "MV", "Mali" => "ML", "Malta" => "MT", "Marshall Islands" => "MH", "Martinique" => "MQ", "Mauritania" => "MR", "Mauritius" => "MU", "Mayotte" => "YT", "Mexico" => "MX", "Micronesia, Federated States of" => "FM", "Moldova, Republic of" => "MD", "Monaco" => "MC", "Mongolia" => "MN", "Montenegro" => "ME", "Montserrat" => "MS", "Morocco" => "MA", "Mozambique" => "MZ", "Myanmar" => "MM", "Namibia" => "NA", "Nauru" => "NR", "Nepal" => "NP", "Netherlands" => "NL", "Netherlands Antilles" => "AN", "New Caledonia" => "NC", "New Zealand" => "NZ", "Nicaragua" => "NI", "Niger" => "NE", "Nigeria" => "NG", "Niue" => "NU", "Norfolk Island" => "NF", "Northern Mariana Islands" => "MP", "Norway" => "NO", "Oman" => "OM", "Pakistan" => "PK", "Palau" => "PW", "Palestinian Territory, Occupied" => "PS", "Panama" => "PA", "Papua New Guinea" => "PG", "Paraguay" => "PY", "Peru" => "PE", "Philippines" => "PH", "Pitcairn" => "PN", "Poland" => "PL", "Portugal" => "PT", "Puerto Rico" => "PR", "Qatar" => "QA", "Reunion" => "RE", "Romania" => "RO", "Russian Federation" => "RU", "Rwanda" => "RW", "Saint Barthélemy" => "BL", "Saint Helena" => "SH", "Saint Kitts and Nevis" => "KN", "Saint Lucia" => "LC", "Saint Martin (French part)" => "MF", "Saint Pierre and Miquelon" => "PM", "Saint Vincent and the Grenadines" => "VC", "Samoa" => "WS", "San Marino" => "SM", "Sao Tome and Principe" => "ST", "Saudi Arabia" => "SA", "Senegal" => "SN", "Serbia" => "RS", "Seychelles" => "SC", "Sierra Leone" => "SL", "Singapore" => "SG", "Slovakia" => "SK", "Slovenia" => "SI", "Solomon Islands" => "SB", "Somalia" => "SO", "South Africa" => "ZA", "South Georgia and the South Sandwich Islands" => "GS", "Spain" => "ES", "Sri Lanka" => "LK", "Sudan" => "SD", "Suriname" => "SR", "Svalbard and Jan Mayen" => "SJ", "Swaziland" => "SZ", "Sweden" => "SE", "Switzerland" => "CH", "Syrian Arab Republic" => "SY", "Taiwan, Province of China" => "TW", "Tajikistan" => "TJ", "Tanzania, United Republic of" => "TZ", "Thailand" => "TH", "Timor Leste" => "TL", "Togo" => "TG", "Tokelau" => "TK", "Tonga" => "TO", "Trinidad and Tobago" => "TT", "Tunisia" => "TN", "Turkey" => "TR", "Turkmenistan" => "TM", "Turks and Caicos Islands" => "TC", "Tuvalu" => "TV", "Uganda" => "UG", "Ukraine" => "UA", "United Arab Emirates" => "AE", "United Kingdom" => "GB", "United States" => "US", "United States Minor Outlying Islands" => "UM", "Uruguay" => "UY", "Uzbekistan" => "UZ", "Vanuatu" => "VU", "Venezuela" => "VE", "Viet Nam" => "VN", "Virgin Islands, British" => "VG", "Virgin Islands, U.S." => "VI", "Wallis and Futuna" => "WF", "Western Sahara" => "EH", "Yemen" => "YE", "Zambia" => "ZM", "Zimbabwe" => "ZW" } end def label_tag_for(name, option_tags = nil, options = {}) label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") content_tag("label", label_text) end def labelled_tabular_form_for(name, object, options, &proc) options[:html] ||= {} options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc) end def back_url_hidden_field_tag back_url = params[:back_url] || request.env['HTTP_REFERER'] back_url = CGI.unescape(back_url.to_s) hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? end def check_all_links(form_name) link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + " | " + link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") end def progress_bar(pcts, options={}) pcts = [pcts, pcts] unless pcts.is_a?(Array) pcts = pcts.collect(&:round) pcts[1] = pcts[1] - pcts[0] pcts << (100 - pcts[1] - pcts[0]) width = options[:width] || '100px;' legend = options[:legend] || '' content_tag('table', content_tag('tr', (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') + (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '') ), :class => 'progress', :style => "width: #{width};") + content_tag('p', legend, :class => 'pourcent') end def context_menu_link(name, url, options={}) options[:class] ||= '' if options.delete(:selected) options[:class] << ' icon-checked disabled' options[:disabled] = true end if options.delete(:disabled) options.delete(:method) options.delete(:confirm) options.delete(:onclick) options[:class] << ' disabled' url = '#' end link_to name, url, options end def help_link(name, options={}) options[:show_name] ||= false #When true, we show the text of the help key next to the link link_to(options[:show_name] ? l('help_' + name.to_s) : '', {:controller => 'help', :action => 'show', :key => name}, {:id =>'help_button_' + name.to_s, :class => 'lbOn icon icon-help'}) end def help_bubble(name, options={}) imagename = options[:image] || "question_mark.gif" image = image_tag(imagename, :class=> "help_question_mark", :id=>"help_image_#{name}") html = link_to(image, {:href => '#'}, {:onclick => "$('#help_image_#{name}').bubbletip('#tip_#{name}', {deltaDirection: 'right', bindShow: 'click'}); return false;"}) html << content_tag(:span, l(name, options), :class => 'tip hidden', :id=>"tip_#{name}") # # end def calendar_for(field_id) include_calendar_headers_tags image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") end def include_calendar_headers_tags unless @calendar_headers_tags_included @calendar_headers_tags_included = true content_for :header_tags do start_of_week = case Setting.start_of_week.to_i when 1 'Calendar._FD = 1;' # Monday when 7 'Calendar._FD = 0;' # Sunday else '' # use language end javascript_include_tag('calendar/calendar') + javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + javascript_tag(start_of_week) + javascript_include_tag('calendar/calendar-setup') + stylesheet_link_tag('calendar') end end end def content_for(name, content = nil, &block) @has_content ||= {} @has_content[name] = true super(name, content, &block) end def has_content?(name) (@has_content && @has_content[name]) || false end # Returns the avatar image tag for the given +user+ if avatars are enabled # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') def avatar(user, options = { }) options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default}) email = nil if user.respond_to?(:mail) email = user.mail elsif user.to_s =~ %r{<(.+?)>} email = $1 end return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil end def render_journal_details(journal) return unless journal html = "" if journal && journal.details && journal.details.count > 0 html = "
      " for detail in journal.details html << "
    • #{show_detail(detail)}
    • " end html << "
    " end content = "" content << textilizable(journal, :notes) content = make_expandable content, 250 css_classes = "wiki" css_classes << " gravatar-margin" if Setting.gravatar_enabled? html << content end def link_to_activity(as) link_to name_for_activity_stream(as), url_for_activity_stream(as), {:class => class_for_activity_stream(as)} end def name_for_activity_stream(as) (as.tracker_name) ? "a #{as.tracker_name.downcase}" : "a " + l("label_#{as.object_type.downcase}") end def class_for_activity_stream(as) (as.object_type.match(/^Issue/)) ? "fancyframe" : "noframe" end def url_for_activity_stream(as) case as.object_type.downcase when 'message' return {:controller => 'messages', :action => 'show', :board_id => 'guess', :id => as.object_id} when 'wikipage' return {:controller => 'wiki', :action => 'index', :id => as.project_id, :page => as.object_name} when 'memberrole' return {:controller => 'projects', :action => 'team', :id => as.project_id} when 'motion' return {:controller => 'motions', :action => 'show', :project_id => as.project_id, :id => as.object_id} else return {:controller => as.object_type.downcase.pluralize, :action => 'show', :id => as.object_id} end end def title_for_activity_stream(as) case as.object_type.downcase when 'memberrole' begin "#{as.indirect_object_phrase || as.object.user.name} is now #{as.object_name}" rescue "New member role" end else format_activity_title(as.object_name) end end def action_times(count) count = count.to_i return nil if count < 2 return " twice" if count == 2 return " #{count.to_s} times" if count > 2 end def avatar_from_id(user_id, options = { }) avatar(User.find(user_id), options) end def button(text, cssclass) return "
    #{text}
    " end def tally_table(motion) content = "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "" content << "
     #{l :label_binding}#{l :label_non_binding}
    #{l(:label_agree)}#{motion.agree}#{motion.agree_nonbind}
    #{l(:label_disagree)}#{motion.disagree}#{motion.disagree_nonbind}
    #{l(:label_total)}#{motion.agree_total}#{motion.agree_total_nonbind}
    " end def tame_bias(number) if number.nil? return "" else number = number.round number > 0 ? "Self:   +#{number}" : number == 0 ? "Self:   No Bias" : "Self:   #{number}" end end def tame_scale(number) if number.nil? "" else number = number.round number == 0 ? "Other: No Bias" : "Other: ±#{number}" end end #depending on credit's status, provides link to activate/deactivate a credit. Project id is the current project being viewed def credit_activation_link(credit, project_id, include_sub_workstreams) return '' if !credit.settled_on.nil? return link_to_remote(l(:button_deactivate), { :url => {:controller => 'credits', :action => 'disable', :id => credit.id, :project_id => project_id, :with_subprojects => include_sub_workstreams} }, :class => 'icon icon-deactivate') if credit.enabled return link_to_remote(l(:button_activate), { :url => {:controller => 'credits', :action => 'enable', :id => credit.id, :project_id => project_id, :with_subprojects => include_sub_workstreams} }, :class => 'icon icon-activate') if !credit.enabled end def login_protocol if ENV['RAILS_ENV'] == "development" 'http' else 'https' end end private def wiki_helper helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) extend helper return self end def link_to_remote_content_update(text, url_params) link_to_remote(text, {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(:params => url_params)} ) end end ================================================ FILE: app/helpers/attachments_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module AttachmentsHelper # Displays view/delete links to the attachments of the given object # Options: # :author -- author names are not displayed if set to false def link_to_attachments(container, options = {}) options.assert_valid_keys(:author) if container.attachments.any? options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) render :partial => 'attachments/links', :locals => {:attachments => container.attachments, :options => options} end end def link_to_attachments_table(container, options = {}) options.assert_valid_keys(:author) if container.attachments.any? options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) render :partial => 'attachments/table', :locals => {:attachments => container.attachments, :options => options} end end def to_utf8(str) str end end ================================================ FILE: app/helpers/groups_helper.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# module GroupsHelper # Options for the new membership projects combo-box def options_for_membership_project_select(user, projects) options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options << project_tree_options_for_select(projects) do |p| {:disabled => (user.projects.include?(p))} end options end def group_settings_tabs tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general}, {:name => 'users', :partial => 'groups/users', :label => :label_user_plural}, {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural} ] end end ================================================ FILE: app/helpers/issue_relations_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module IssueRelationsHelper def collection_for_relation_type_select values = IssueRelation::TYPES values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]} end end ================================================ FILE: app/helpers/issues_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module IssuesHelper include ApplicationHelper def render_issue_tooltip(issue) @cached_label_start_date ||= l(:field_start_date) @cached_label_due_date ||= l(:field_due_date) @cached_label_assigned_to ||= l(:field_assigned_to) link_to_issue(issue) + "

    " + "#{@cached_label_start_date}: #{format_date(issue.start_date)}
    " + "#{@cached_label_due_date}: #{format_date(issue.due_date)}
    " + "#{@cached_label_assigned_to}: #{issue.assigned_to}
    " end def sidebar_queries unless @sidebar_queries # User can see public queries and his own queries visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)]) # Project specific queries and global queries visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) @sidebar_queries = Query.find(:all, :select => 'id, name', :order => "name ASC", :conditions => visible.conditions) end @sidebar_queries end def tags(issue, editable) if editable text_field :issue, :tag_list, :class => 'gt-form-text issue-tags', :value => issue.tag_list.join(",") else html = '' issue.tag_list.each {|t| html = html + content_tag('span', t, :class => "tag")} content_tag('div', html, :class => "tagsoutput") end end def issues_to_csv(issues, project = nil) ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') decimal_separator = l(:general_csv_decimal_separator) export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| # csv header fields headers = [ "#", l(:field_status), l(:field_project), l(:field_tracker), l(:field_subject), l(:field_assigned_to), l(:field_author), l(:field_start_date), l(:field_due_date), l(:field_estimated_hours), l(:field_created_at), l(:field_updated_at) ] # Description in the last column headers << l(:field_description) csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } # csv lines issues.each do |issue| fields = [issue.id, issue.status.name, issue.project.name, issue.tracker.name, issue.subject, issue.assigned_to, issue.author.name, format_date(issue.start_date), format_date(issue.due_date), issue.estimated_hours.to_s.gsub('.', decimal_separator), format_time(issue.created_at), format_time(issue.updated_at) ] fields << issue.description csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } end end export end def collection_for_project_members_select values = @issue.project.root.all_members.collect {|p| [p.name, p.user.id]} existing_team = @issue.team_votes.collect {|p| [User.find(p.user_id).name, p.user_id]} values - existing_team end end ================================================ FILE: app/helpers/journals_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # module JournalsHelper def render_notes(journal, editable, options={}) content = '' editable ||= false; links = [] if !journal.notes.blank? links << link_to_in_place_notes_editor(l(:button_edit), "journal-#{journal.id}-notes", { :controller => 'journals', :action => 'edit', :id => journal }, :title => l(:button_edit), :class => 'icon icon-edit') if editable end content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty? content << textilizable(journal, :notes) css_classes = "wiki" css_classes << " editable" if editable css_classes << " gravatar-margin" if Setting.gravatar_enabled? content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes) end def render_votes(journal, options={}) votingcontent = '' #We show total votes regardless votingcontent << " " + String(journal.votes_for - journal.votes_against) + " points" # Voting on journal items unless journal.user_id == User.current.id || !User.current.logged? || User.current.voted_on?(journal) votingcontent << link_to_remote(image_tag('/images/aupgray.gif', :size => "15x14", :border => 0), { :url => user_journal_votes_path(User.current, journal, :vote => :true, :format => :js, :voteable_type => "journal"), :method => :post }) votingcontent << link_to_remote(image_tag('/images/adowngray.gif', :size => "15x14", :border => 0), { :url => user_journal_votes_path(User.current, journal, :vote => :false, :format => :js, :voteable_type => "journal"), :method => :post }) end content_tag('span', votingcontent, :id => "votes_" + String(journal.id), :class => 'journalvote') end def link_to_in_place_notes_editor(text, field_id, url, options={}) onclick = "$.ajax({type: 'GET', url: '#{url_for(url)}'});return false;" link_to text, '#', options.merge(:onclick => onclick) end end ================================================ FILE: app/helpers/messages_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module MessagesHelper def link_to_message(message) return '' unless message link_to h(truncate(message.subject, :length => 60)), :controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root, :anchor => (message.parent_id ? "message-#{message.id}" : nil) end end ================================================ FILE: app/helpers/motions_helper.rb ================================================ module MotionsHelper def render_title_date end_date = @motion.ends_on if (!@motion.active?) logger.info { DateTime.now } logger.info { "message" } return "Voting ended #{distance_of_time_in_words(Time.now,end_date)} ago" else return "Voting ends in #{distance_of_time_in_words(Time.now,end_date)}" end end end ================================================ FILE: app/helpers/my_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module MyHelper def describe(amount) amount == -1 ? 'unlimited' : amount end def my_issues_tabs tabs = [ {:name => 'recent', :partial => 'issues/list_very_simple', :label => :label_recent_issues, :label_ending => " (#{@recent_issues.length})", :locals => {:issues => @recent_issues} }, {:name => 'assigned', :partial => 'issues/list_very_simple', :label => :label_assigned_to_me_issues, :label_ending => " (#{@assigned_issues.length})", :locals => {:issues => @assigned_issues} }, {:name => 'joined', :partial => 'issues/list_very_simple', :label => :label_joined_issues, :label_ending => " (#{@joined_issues.length})", :locals => {:issues => @joined_issues} }, {:name => 'added', :partial => 'issues/list_very_simple', :label => :label_added_issues, :label_ending => " (#{@added_issues.length})", :locals => {:issues => @added_issues} }, {:name => 'watched', :partial => 'issues/list_very_simple', :label => :label_watched_issues, :label_ending => " (#{@watched_issues.length})", :locals => {:issues => @watched_issues} } ] end def my_projects_tabs tabs = [ {:name => 'all', :partial => 'my/project_list', :label => :label_projects_all, :locals => { :my_projects => @all_projects, :table_head => l(:label_projects_all), :table_id => "all_my_projects_table", :table_bottom_link => link_to(l(:label_project_new), {:controller => :projects, :action => :add}, :class => "gt-btn-blue-large"), :no_data_help => l(:help_no_workstreams), :no_data_link => link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}) + "
    or
    " + link_to(l(:label_browse_workstreams), {:controller => :projects, :action => :index}) } }, {:name => 'active', :partial => 'my/project_list', :label => :label_projects_active_in, :locals => { :my_projects => @active_projects, :table_head => l(:label_projects_active_in), :table_id => "active_projects_table", :table_bottom_link => link_to(l(:label_browse_workstreams), {:controller => :projects, :action => :index}, :class => "gt-btn-blue-large"), :no_data_help => l(:help_no_workstreams_active), :no_data_link => link_to(l(:label_browse_workstreams), {:controller => :projects, :action => :index}) } }, {:name => 'started', :partial => 'my/project_list', :label => :label_projects_i_started, :locals => { :my_projects => @my_projects, :table_head => l(:label_projects_i_started), :table_id => "my_projects_table", :table_bottom_link => link_to(l(:label_project_new), {:controller => :projects, :action => :add}, :class => "gt-btn-blue-large"), :no_data_help => l(:help_no_my_workstreams), :no_data_link => link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}) } }, ] end def upgrade_options(user) if user.plan.code == Plan::FREE_CODE link_to "Upgrade", {:controller => :my, :action => :upgrade}, :class => "gt-btn-blue-large" else link_to "Upgrade / Downgrade", {:controller => :my, :action => :upgrade}, :class => "gt-btn-blue-large" end end end ================================================ FILE: app/helpers/news_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module NewsHelper end ================================================ FILE: app/helpers/projects_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module ProjectsHelper def project_settings_tabs tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural} ] tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} end def project_image(project) begin content_tag('div', (image_tag formatted_project_path(@project, :png)), :class => "gt-sidebar-logo") if project && project.has_image? rescue end end def nomination_links(member,project) return if member.user_id == User.current.id return unless User.current.binding_voter_of?(project) content = '

    ' #Link to nominate to core if user is a member content << link_to(l(:label_nominate_to_core_team), project_motions_path(project, "motion[variation]" => Motion::VARIATION_NEW_CORE, "motion[concerned_user_id]" => member.user_id), :class => 'icon icon-cr-offer', :confirm => "Are you sure you want to start a motion to nominate #{member.name} to the Core Team?", :method => :post) << '    ' if member.roles.first.id == Role.member.id #Link to drop from core if user is a core member content << link_to(l(:label_drop_from_core_team), project_motions_path(project, "motion[variation]" => Motion::VARIATION_FIRE_CORE, "motion[concerned_user_id]" => member.user_id), :method => :post, :confirm => "Are you sure you want to start a motion to remove #{member.name} from the Core Team and make her a Member?", :class => 'icon icon-cr-decline') << ' ' if member.roles.first.id == Role.core_member.id #Link to nominate to member if user is contributor, and current user is a binding member content << link_to(l(:label_nominate_to_member), project_motions_path(project, "motion[variation]" => Motion::VARIATION_NEW_MEMBER, "motion[concerned_user_id]" => member.user_id), :method => :post, :confirm => "Are you sure you want to start a motion to nominate #{member.name} as a Member?", :class => 'icon icon-cr-offer') << ' ' if member.roles.first.id == Role.contributor.id #Link to drop from member if user is member, and current user is binding member content << link_to(l(:label_drop_from_member), project_motions_path(project, "motion[variation]" => Motion::VARIATION_FIRE_MEMBER, "motion[concerned_user_id]" => member.user_id), :confirm => "Are you sure you want to start a motion to remove the membership of #{member.name}", :method => :post, :class => 'icon icon-cr-decline') << ' ' if member.roles.first.id == Role.member.id content << "" if project.allowed_parents.include?(nil) options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected) content_tag('select', options, :name => 'project[parent_id]') end # Renders a tree of projects as a nested set of unordered lists # The given collection may be a subset of the whole project tree # (eg. some intermediate nodes are private and can not be seen) def render_project_hierarchy(projects) s = '' if projects.any? ancestors = [] projects.each do |project| if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) s << "

      \n" else ancestors.pop s << "" while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) ancestors.pop s << "
    \n" end end classes = (ancestors.empty? ? 'root' : 'child') s << "
  • " + link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") s << "
    #{textilizable(project.short_description, :project => project)}
    " unless project.description.blank? s << "
    \n" ancestors << project end s << ("
  • \n" * ancestors.size) end s end # Renders the "add item" quick jump box. def render_new_item_jump_box s = '' s << '' end end ================================================ FILE: app/helpers/queries_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module QueriesHelper def operators_for_select(filter_type) Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} end def column_header(column) column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, :default_order => column.default_order) : content_tag('th', column.caption) end def column_content(column, issue) value = column.value(issue) case value.class.name when 'String' if column.name == :subject link_to(h(value), {:controller => 'issues', :action => 'show', :id => issue}, :class => "fancyframe") else h(value) end when 'Time' format_time(value) when 'Date' format_date(value) when 'Fixnum', 'Float' value.to_s when 'User' link_to_user value when 'Project' link_to(h(value), :controller => 'projects', :action => 'show', :id => value) when 'TrueClass' l(:general_text_Yes) when 'FalseClass' l(:general_text_No) else h(value) end end end ================================================ FILE: app/helpers/reports_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module ReportsHelper def aggregate(data, criteria) a = 0 data.each { |row| match = 1 criteria.each { |k, v| match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t")) } unless criteria.nil? a = a + row["total"].to_i if match == 1 } unless data.nil? a end def aggregate_link(data, criteria, *args) a = aggregate data, criteria a > 0 ? link_to(a, *args) : '-' end end ================================================ FILE: app/helpers/retros_helper.rb ================================================ module RetrosHelper def render_title_date end_date = @retro.created_at.advance(:days => Setting::DEFAULT_RETROSPECTIVE_LENGTH) if (@retro.ended?) return "ended #{distance_of_time_in_words(Time.now,end_date)} ago" else return "ends in #{distance_of_time_in_words(Time.now,end_date)}" end end def team_from_issue(issue) issue.team_votes.collect{|iv| link_to_user_from_id iv.user_id }.join(", ") end def accuracy_display(self_bias,magnitude) return "
    Didn't vote" if self_bias.nil? && magnitude.nil? content = "" content << "
    #{tame_bias(self_bias)}
    #{tame_scale(magnitude)}" end end ================================================ FILE: app/helpers/search_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module SearchHelper def highlight_tokens(text, tokens) return text unless text && tokens && !tokens.empty? re_tokens = tokens.collect {|t| Regexp.escape(t)} regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE result = '' text.split(regexp).each_with_index do |words, i| if result.length > 1200 # maximum length of the preview reached result << '...' break end words = words.mb_chars if i.even? result << h(words.length > 100 ? "#{words.slice(0..44)} ... #{words.slice(-45..-1)}" : words) else t = (tokens.index(words.downcase) || 0) % 4 result << content_tag('span', h(words), :class => "highlight token-#{t}") end end result end def type_label(t) l("label_#{t.singularize}_plural") end def project_select_tag options = [[l(:label_project_all), 'all']] options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty? options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty? options << [@project.name, ''] unless @project.nil? select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1 end def render_results_by_type(results_by_type) return unless results_by_type links = [] # Sorts types by results count results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t| c = results_by_type[t] next if c == 0 text = "#{type_label(t)} (#{c})" links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1) end ('
      ' + links.map {|link| content_tag('li', link)}.join(' ') + '
    ') unless links.empty? end end ================================================ FILE: app/helpers/settings_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module SettingsHelper def administration_settings_tabs tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general}, {:name => 'display', :partial => 'settings/display', :label => :label_display}, {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication}, {:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural}, {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking}, {:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification}, {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails} ] end def setting_select(setting, choices, options={}) if blank_text = options.delete(:blank) choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices end setting_label(setting, options) + select_tag("settings[#{setting}]", options_for_select(choices, Setting.send(setting).to_s), options) end def setting_multiselect(setting, choices, options={}) setting_values = Setting.send(setting) setting_values = [] unless setting_values.is_a?(Array) setting_label(setting, options) + hidden_field_tag("settings[#{setting}][]", '') + choices.collect do |choice| text, value = (choice.is_a?(Array) ? choice : [choice, choice]) content_tag('label', check_box_tag("settings[#{setting}][]", value, Setting.send(setting).include?(value)) + text.to_s, :class => 'block' ) end.join end def setting_text_field(setting, options={}) setting_label(setting, options) + text_field_tag("settings[#{setting}]", Setting.send(setting), options) end def setting_text_area(setting, options={}) setting_label(setting, options) + text_area_tag("settings[#{setting}]", Setting.send(setting), options) end def setting_check_box(setting, options={}) setting_label(setting, options) + hidden_field_tag("settings[#{setting}]", 0) + check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options) end def setting_label(setting, options={}) label = options.delete(:label) label != false ? content_tag("label", l(label || "setting_#{setting}")) : '' end end ================================================ FILE: app/helpers/sort_helper.rb ================================================ # Helpers to sort tables using clickable column headers. # # Author: Stuart Rackham , March 2005. # Shereef Bishay, 2009 # License: This source code is released under the MIT license. # # - Consecutive clicks toggle the column's sort order. # - Sort state is maintained by a session hash entry. # - CSS classes identify sort column and state. # - Typically used in conjunction with the Pagination module. # # Example code snippets: # # Controller: # # helper :sort # include SortHelper # # def list # sort_init 'last_name' # sort_update %w(first_name last_name) # @items = Contact.find_all nil, sort_clause # end # # Controller (using Pagination module): # # helper :sort # include SortHelper # # def list # sort_init 'last_name' # sort_update %w(first_name last_name) # @contact_pages, @items = paginate :contacts, # :order_by => sort_clause, # :per_page => 10 # end # # View (table header in list.rhtml): # # # # <%= sort_header_tag('id', :title => 'Sort by contact ID') %> # <%= sort_header_tag('last_name', :caption => 'Name') %> # <%= sort_header_tag('phone') %> # <%= sort_header_tag('address', :width => 200) %> # # # # - Introduces instance variables: @sort_default, @sort_criteria # - Introduces param :sort # module SortHelper class SortCriteria def initialize @criteria = [] end def available_criteria=(criteria) unless criteria.is_a?(Hash) criteria = criteria.inject({}) {|h,k| h[k] = k; h} end @available_criteria = criteria end def from_param(param) @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} normalize! end def criteria=(arg) @criteria = arg normalize! end def to_param @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') end def to_sql sql = @criteria.collect do |k,o| if s = @available_criteria[k] (o ? s.to_a : s.to_a.collect {|c| "#{c} DESC"}).join(', ') end end.compact.join(', ') sql.blank? ? nil : sql end def add!(key, asc) @criteria.delete_if {|k,o| k == key} @criteria = [[key, asc]] + @criteria normalize! end def add(*args) r = self.class.new.from_param(to_param) r.add!(*args) r end def first_key @criteria.first && @criteria.first.first end def first_asc? @criteria.first && @criteria.first.last end def empty? @criteria.empty? end private def normalize! @criteria ||= [] @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]} @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria @criteria.slice!(3) self end end def sort_name controller_name + '_' + action_name + '_sort' end # Initializes the default sort. # Examples: # # sort_init 'name' # sort_init 'id', 'desc' # sort_init ['name', ['id', 'desc']] # sort_init [['name', 'desc'], ['id', 'desc']] # def sort_init(*args) case args.size when 1 @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] when 2 @sort_default = [[args.first, args.last]] else raise ArgumentError end end # Updates the sort state. Call this in the controller prior to calling # sort_clause. # - criteria can be either an array or a hash of allowed keys # def sort_update(criteria) @sort_criteria = SortCriteria.new @sort_criteria.available_criteria = criteria @sort_criteria.from_param(params[:sort] || session[sort_name]) @sort_criteria.criteria = @sort_default if @sort_criteria.empty? session[sort_name] = @sort_criteria.to_param end # Clears the sort criteria session data # def sort_clear session[sort_name] = nil end # Returns an SQL sort clause corresponding to the current sort state. # Use this to sort the controller's table items collection. # def sort_clause @sort_criteria.to_sql end # Returns a link which sorts by the named column. # # - column is the name of an attribute in the sorted record collection. # - the optional caption explicitly specifies the displayed link text. # - 2 CSS classes reflect the state of the link: sort and asc or desc # def sort_link(column, caption, default_order) css, order = nil, default_order if column.to_s == @sort_criteria.first_key if @sort_criteria.first_asc? css = 'sort asc' order = 'desc' else css = 'sort desc' order = 'asc' end end caption = column.to_s.humanize unless caption sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } # don't reuse params if filters are present url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options) # Add project_id to url_options url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) link_to_remote(caption, {:update => "content", :url => url_options, :method => :get}, {:href => url_for(url_options), :class => css}) end # Returns a table header tag with a sort link for the named column # attribute. # # Options: # :caption The displayed link name (defaults to titleized column name). # :title The tag's 'title' attribute (defaults to 'Sort by :caption'). # # Other options hash entries generate additional table header tag attributes. # # Example: # # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %> # def sort_header_tag(column, options = {}) caption = options.delete(:caption) || column.to_s.humanize default_order = options.delete(:default_order) || 'asc' options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title] content_tag('th', sort_link(column, caption, default_order), options) end end ================================================ FILE: app/helpers/users_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module UsersHelper def users_status_options_for_select(selected) user_count_by_status = User.count(:group => 'status').to_hash options_for_select([[l(:label_all), ''], ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1], ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2], ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected) end # Options for the new membership projects combo-box def options_for_membership_project_select(user, projects) options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options << project_tree_options_for_select(projects) do |p| {:disabled => (user.projects.include?(p))} end options end def change_status_link(user) url = {:controller => 'users', :action => 'edit', :id => user, :page => params[:page], :status => params[:status], :tab => nil} if user.locked? link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' elsif user.registered? link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :post, :class => 'icon icon-unlock' elsif user != User.current link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :post, :class => 'icon icon-lock' end end def user_settings_tabs tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} ] tabs end def reputation_value(reputation_type, reputation_value) case reputation_type when Reputation::VARIATION_SELF_BIAS tame_bias(reputation_value) when Reputation::VARIATION_SCALE_BIAS tame_scale(reputation_value) end end def reputation_project(reputation) if reputation.project_id != 0 link_to(h(reputation.project.name_with_ancestors), :controller => 'projects', :action => 'show', :id => reputation.project) else "Platform Wide" end end end ================================================ FILE: app/helpers/watchers_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module WatchersHelper # Valid options # * :id - the element id # * :replace - a string or array of element ids that will be # replaced def watcher_tag(object, user, options={:replace => 'watcher'}) id = options[:id] id ||= options[:replace] if options[:replace].is_a? String content_tag("span", watcher_link(object, user, options), :id => id) end # Valid options # * :replace - a string or array of element ids that will be # replaced def watcher_link(object, user, options={:replace => 'watcher'}) return '' unless user && user.logged? && object.respond_to?('watched_by?') watched = object.watched_by?(user) url = {:controller => 'watchers', :action => (watched ? 'unwatch' : 'watch'), :object_type => object.class.to_s.underscore, :object_id => object.id, :replace => options[:replace]} link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)), {:url => url}, :href => url_for(url), :class => (watched ? 'icon icon-fav action-state-button' : 'icon icon-fav-off action-state-button')) end # Returns a comma separated list of users watching the given object def watchers_list(object) remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) object.watcher_users.collect do |user| s = content_tag('span', link_to_user(user), :class => 'user') if remove_allowed url = {:controller => 'watchers', :action => 'destroy', :object_type => object.class.to_s.underscore, :object_id => object.id, :user_id => user} s += ' ' + link_to_remote(image_tag('delete.png'), {:url => url}, :href => url_for(url), :style => "vertical-align: middle") end s end.join(",\n") end end ================================================ FILE: app/helpers/wiki_helper.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module WikiHelper def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) s = '' pages.select {|p| p.parent == parent}.each do |page| attrs = "value='#{page.id}'" attrs << " selected='selected'" if selected == page indent = (level > 0) ? (' ' * level * 2 + '» ') : nil s << "\n" + wiki_page_options_for_select(pages, selected, page, level + 1) end s end def html_diff(wdiff) words = wdiff.words.collect{|word| h(word)} words_add = 0 words_del = 0 dels = 0 del_off = 0 wdiff.diff.diffs.each do |diff| add_at = nil add_to = nil del_at = nil deleted = "" diff.each do |change| pos = change[1] if change[0] == "+" add_at = pos + dels unless add_at add_to = pos + dels words_add += 1 else del_at = pos unless del_at deleted << ' ' + h(change[2]) words_del += 1 end end if add_at words[add_at] = '' + words[add_at] words[add_to] = words[add_to] + '' end if del_at words.insert del_at - del_off + dels + words_add, '' + deleted + '' dels += 1 del_off += words_del words_del = 0 end end simple_format_without_paragraph(words.join(' ')) end end ================================================ FILE: app/models/activity_stream.rb ================================================ # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. # activity_stream.rb provides the model ActivityStream class ActivityStream < ActiveRecord::Base # status levels, for generating activites that don't display VISIBLE = 0 # Noraml visible activity DEBUG = 1 # Test activity used for debugginng INTERNAL = 2 # Internal system activity DELETED = 3 # A deleted activity (soft delete) belongs_to :actor, :polymorphic => true belongs_to :object, :polymorphic => true belongs_to :indirect_object, :polymorphic => true belongs_to :project named_scope :recent, {:conditions => "activity_streams.created_at > '#{(Time.now.advance :days => Setting::DAYS_FOR_ACTIVE_MEMBERSHIP * -1).to_s}'"} def before_save self.is_public = self.project.is_public if self.project self.is_public = false if self.hidden_from_user_id > 0 end # Finds the recent activities for a given actor, and honors # the users activity_stream_preferences. Please see the README # for an example usage. def self.recent_actors(actor, location, limit=12) unless actor.class.name == ACTIVITY_STREAM_USER_MODEL find(:all, :conditions => {:actor_id => actor.id, :actor_type => actor.class.name, :status => 0}, :order => "created_at DESC", :limit => limit, :include => [:actor, :object, :indirect_object]) else # FIXME: We really want :include => [:actor, :object], however, when # the "p.id" => nil condition prevents polymorphic :include from working find(:all, :joins => self.preference_join(location), :conditions => [ 'actor_id = ? and actor_type = ? and status = ? and p.id IS NULL', actor.id, actor.class.name, 0 ], :order => "created_at DESC", :limit => limit) end end # Finds the recent activities for a given actor, and honors # the users activity_stream_preferences. Please see the README # for a sample usage. def self.recent_objects(object, location, limit=12) # FIXME: We really want :include => [:actor, :object], however, when # the "p.id" => nil condition prevents polymorphic :include from working find(:all, :joins => self.preference_join(location), :conditions => [ "object_id = ? and object_type = ? and status = ? and p.id IS NULL", object.id, object.class.name, 0 ], :order => "created_at DESC", :limit => limit) end def self.preference_join(location) # :nodoc: # location is not tainted as it is a symbol from # the code "LEFT OUTER JOIN activity_stream_preferences p \ ON #{ACTIVITY_STREAM_USER_MODEL_ID} = actor_id \ AND actor_type = '#{ACTIVITY_STREAM_USER_MODEL}' \ AND activity_streams.activity = p.activity \ AND location = '#{location.to_s}'" end def self.fetch(user_id, project_id, with_subprojects, limit, max_created_at = nil) max_created_at = DateTime.now if max_created_at.nil? || max_created_at == "" length = limit || Setting::ACTIVITY_STREAM_LENGTH if limit length = limit end with_subprojects ||= true project_id.nil? ? project = nil : project = Project.find(project_id) user = User.find(user_id) if user_id return [] if user && with_subprojects == "custom" && user.projects.empty?#Customized activity stream for user, but user doesn't belong to any projects conditions = {} conditions[:actor_id] = user_id unless user_id.nil? || with_subprojects == "custom" conditions[:project_id] = user.projects.collect{|m| m.id} if !user.nil? && with_subprojects == "custom" && !user.projects.empty? #Customized activity stream for user conditions[:project_id] = project.id if project && !with_subprojects conditions[:project_id] = project.sub_project_array_visible_to(User.current) if project && with_subprojects project_specified = conditions[:project_id] #temp variable for later use conditions = conditions.to_array_conditions logger.info { "conditions #{conditions.inspect}" } conditions[0] += " AND " unless conditions[0].empty? conditions[0] += " created_at >= ? AND created_at <= ?" conditions.push DateTime.now - 20.year conditions.push max_created_at conditions[0] += " AND hidden_from_user_id <> ?" conditions.push User.current.id unless project_specified conditions[0] += " AND ((is_public = true) OR (project_id in (?)))" conditions.push User.current.projects.collect{|m| m.id} end unless User.current.logged? conditions[0] += " AND (is_public = true)" end activities_by_item = ActivityStream.all(:conditions => conditions, :limit => length, :order => "created_at desc").group_by {|a| a.object_type.to_s + a.object_id.to_s} activities_by_item.each_pair do |key,value| activities_by_item[key] = value.sort_by{|i| - i[:created_at].to_i} end activities_by_item.sort_by{|g| - g[1][0][:created_at].to_i} end # Soft Delete in as some activites are necessary for site stats def soft_destroy self.update_attribute(:status, DELETED) end end ================================================ FILE: app/models/activity_stream_preference.rb ================================================ # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. # activity_stream_preference.rb provides the model ActivityStreamPreference # # ActivityStreamPreference is the model used to keep track of preferences for the Activity Stream Plug-in class ActivityStreamPreference < ActiveRecord::Base end ================================================ FILE: app/models/activity_stream_total.rb ================================================ # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. # activity_stream_total.rb provides the model ActivityStreamTotal # # ActivityStreamTotal is the model used to keep total counts on related activities # class ActivityStreamTotal < ActiveRecord::Base belongs_to :object, :polymorphic => true end ================================================ FILE: app/models/attachment.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require "digest/md5" class Attachment < ActiveRecord::Base belongs_to :container, :polymorphic => true belongs_to :author, :class_name => "User", :foreign_key => "author_id" validates_presence_of :filename, :author validates_length_of :filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255 after_validation :put_to_s3 before_destroy :delete_from_s3 acts_as_event :title => :filename, :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} cattr_accessor :storage_path unloadable # Send unloadable so it will not be unloaded in development attr_accessor :s3_access_key_id, :s3_secret_acces_key, :s3_bucket, :s3_bucket @@storage_path = "#{RAILS_ROOT}/files" def validate if self.filesize > Setting.attachment_max_size.to_i.kilobytes errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes) end end def put_to_s3 if @temp_file && (@temp_file.size > 0) logger.debug("Uploading to #{RedmineS3::Connection.uri}/#{disk_filename}") RedmineS3::Connection.put(disk_filename, @temp_file.read) RedmineS3::Connection.publicly_readable!(disk_filename) md5 = Digest::MD5.new self.digest = md5.hexdigest end @temp_file = nil # so that the model's original after_save block skips writing to the fs end def delete_from_s3 if ENV['RACK_ENV'] == 'production' logger.debug("Deleting #{RedmineS3::Connection.uri}/#{disk_filename}") RedmineS3::Connection.delete(disk_filename) end end def file=(incoming_file) unless incoming_file.nil? @temp_file = incoming_file if @temp_file.size > 0 self.filename = sanitize_filename(@temp_file.original_filename) self.disk_filename = Attachment.disk_filename(filename) self.content_type = @temp_file.content_type.to_s.chomp self.filesize = @temp_file.size end end end def file nil end # Copies the temporary file to its final location # and computes its MD5 hash def before_save logger.debug("entering before save") if @temp_file && (@temp_file.size > 0) logger.debug("saving '#{self.diskfile}'") md5 = Digest::MD5.new File.open(diskfile, "wb") do |f| buffer = "" while (buffer = @temp_file.read(8192)) f.write(buffer) md5.update(buffer) end end self.digest = md5.hexdigest end # Don't save the content type if it's longer than the authorized length if self.content_type && self.content_type.length > 255 self.content_type = nil end end # Deletes file on the disk def after_destroy File.delete(diskfile) if !filename.blank? && File.exist?(diskfile) end # Returns file's location on disk def diskfile "#{@@storage_path}/#{self.disk_filename}" end def increment_download increment!(:downloads) end def project container.project end def visible?(user=User.current) container.attachments_visible?(user) end def deletable?(user=User.current) container.attachments_deletable?(user) end def image? self.filename =~ /\.(jpe?g|gif|png)$/i end def is_text? Redmine::MimeType.is_type?('text', filename) end def is_diff? self.filename =~ /\.(patch|diff)$/i end # Returns true if the file is readable def readable? File.readable?(diskfile) end private def sanitize_filename(value) # get only the filename, not the whole path just_filename = value.gsub(/^.*(\\|\/)/, '') # NOTE: File.basename doesn't work right with Windows paths on Unix # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/')) # Finally, replace all non alphanumeric, hyphens or periods with underscore @filename = just_filename.gsub(/[^\w\.\-]/,'_') end # Returns an ASCII or hashed filename def self.disk_filename(filename) df = DateTime.now.strftime("%y%m%d%H%M%S") + "_" if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} df << filename else df << Digest::MD5.hexdigest(filename) # keep the extension if any df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} end df end end ================================================ FILE: app/models/auth_source.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class AuthSource < ActiveRecord::Base has_many :users validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 60 def authenticate(login, password) end def test_connection end def auth_method_name "Abstract" end # Try to authenticate a user not yet registered against available sources def self.authenticate(login, password) AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source| begin logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? attrs = source.authenticate(login, password) rescue => e logger.error "Error during authentication: #{e.message}" attrs = nil end return attrs if attrs end return nil end end ================================================ FILE: app/models/auth_source_ldap.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'net/ldap' require 'iconv' class AuthSourceLdap < AuthSource validates_presence_of :host, :port, :attr_login validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true validates_numericality_of :port, :only_integer => true before_validation :strip_ldap_attributes def after_initialize self.port = 389 if self.port == 0 end def authenticate(login, password) return nil if login.blank? || password.blank? attrs = [] # get user's DN ldap_con = initialize_ldap_con(self.account, self.account_password) login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) dn = String.new ldap_con.search( :base => self.base_dn, :filter => object_filter & login_filter, # only ask for the DN if on-the-fly registration is disabled :attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry| dn = entry.dn attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), :auth_source_id => self.id ] if onthefly_register? end return nil if dn.empty? logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug? # authenticate user ldap_con = initialize_ldap_con(dn, password) return nil unless ldap_con.bind # return user's attributes logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? attrs rescue Net::LDAP::LdapError => text raise "LdapError: " + text end # test the connection to the LDAP def test_connection ldap_con = initialize_ldap_con(self.account, self.account_password) ldap_con.open { } rescue Net::LDAP::LdapError => text raise "LdapError: " + text end def auth_method_name "LDAP" end private def strip_ldap_attributes [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? end end def initialize_ldap_con(ldap_user, ldap_password) options = { :host => self.host, :port => self.port, :encryption => (self.tls ? :simple_tls : nil) } options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? Net::LDAP.new options end def self.get_attr(entry, attr_name) if !attr_name.blank? entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] end end end ================================================ FILE: app/models/board.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Board < ActiveRecord::Base belongs_to :project has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_at DESC" has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_at DESC" belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id acts_as_list :scope => :project_id acts_as_watchable validates_presence_of :name, :description validates_length_of :name, :maximum => 30 validates_length_of :description, :maximum => 255 def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_messages, project) end def to_s name end def reset_counters! self.class.reset_counters!(id) end # Updates topics_count, messages_count and last_message_id attributes for +board_id+ def self.reset_counters!(board_id) board_id = board_id.to_i update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," + " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," + " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", ["id = ?", board_id]) end end ================================================ FILE: app/models/comment.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Comment < ActiveRecord::Base belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates_presence_of :commented, :author, :comments after_save :send_mentions def send_mentions Mention.parse(self, self.author_id) end def title case self.commented_type when "News" self.commented.title end end def mention(mentioner_id, mentioned_id, mention_text) Notification.create :recipient_id => mentioned_id, :variation => 'mention', :params => {:mention_text => self.comments, :url => {:controller => self.commented_type.to_s.pluralize.downcase, :action => "show", :id => self.commented_id}, :title => self.title}, :sender_id => mentioner_id, :source_id => self.id, :source_type => "Comment(#{self.commented_type})" end end ================================================ FILE: app/models/credit.rb ================================================ class Credit < ActiveRecord::Base default_value_for :issued_on do Time.now end #Constants ROUNDING_LEVEL = 2 #How many digits to round down to when paying off belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id' belongs_to :project after_create :issue_shares def issue_day self.issued_on.strftime('%D') end def disable self.enabled = false return self.save end def enable self.enabled = true return self.save end #For every credit that is issue, a corresponding share is issued def issue_shares Share.create! :amount => amount, :owner => owner, :project => project, :issued_on => issued_on unless previously_issued #We don't create shares if we're creating credit for a past issue date (i.e. in case of an incomplete payoff) end def previously_issued (issued_on - created_at) > 2 #if created date is different than issued on date (by more than a few milliseconds) then this was a previously issued credit, that's being recreated as portion of shares already given out end def settled? return !self.settled.nil? end def pay_out(pay_amount) pay_amount = Credit.round(pay_amount) return false if pay_amount > amount #TODO raise an error here? original_amount = self.amount #Saving current record self.amount = pay_amount self.settled_on = Time.now self.save #We cashed out some credits, so we set some shares to expire Share.set_expiration(owner,project,pay_amount,Time.now + (Time.now - issued_on)) #Wasn't fully paid out, we need to create a new entry with the remaining unissued credit if original_amount > pay_amount Credit.create! :amount => Credit.round(original_amount-pay_amount), :owner => owner, :project => project, :issued_on => issued_on end end def self.round(x) (x * 10**ROUNDING_LEVEL).floor.to_f / 10**ROUNDING_LEVEL #rounds down end #Pays out a certain amount of credit for a certain project (e.g. payout $6000 for the website project) #Owners on top of the stack are paid out first, and shares are updated accordingly def self.settle(project,amount) remaining_amount = amount Credit.find(:all,:conditions => {:project_id => project, :enabled => true, :settled_on => nil}, :order => 'issued_on ASC').group_by(&:issue_day).each do |day,credits| #Looping once for each day day_amount = credits.inject(0) {|sum, credit| sum = sum + credit.amount} if remaining_amount >= day_amount credits.each {|credit| credit.pay_out(credit.amount)} #payoff full amount of each credit else credits.each {|credit| credit.pay_out(credit.amount * remaining_amount / day_amount)} #payoff full amount of each credit break; #Stop looping through days end remaining_amount = remaining_amount - day_amount break if remaining_amount <= 0 end remaining_amount <= 0 ? 0 : remaining_amount end #transfers a certain amount of credit for a certain user for a certain project to another user (e.g. shereef gives $6000 from the website project to adele) #newest credits are transfered first #returns total paid def self.transfer(sender,recipient,project,amount, note) remaining_amount = amount Credit.find(:all,:conditions => {:project_id => project.id, :settled_on => nil, :owner_id => sender.id}, :order => 'issued_on DESC').each do |credit| #Looping once for each day if remaining_amount >= credit.amount credit.owner_id = recipient.id credit.save remaining_amount = remaining_amount - credit.amount else credit.amount = Credit.round(credit.amount - remaining_amount) credit.save Credit.create! :amount => Credit.round(remaining_amount), :owner => recipient, :project => project remaining_amount = 0 break; #Stop looping through days end break if remaining_amount <= 0 end total_paid = Credit.round(amount - remaining_amount) CreditTransfer.create! :sender => sender, :recipient => recipient, :project => project, :amount => total_paid, :note => note if total_paid > 0 return total_paid end end ================================================ FILE: app/models/credit_distribution.rb ================================================ class CreditDistribution < ActiveRecord::Base after_create :add_credits belongs_to :project belongs_to :retro belongs_to :user GIFT = -1 #value in retro_id when distribution is a result of a gift, not a retrospective EXPENSE = -2 #value in retro_id when distribution is a result of an expense, not a retrospective HOURLY = -3 # value in retro_id when distribution is a result of an hourly, not a retrospective def add_credits Credit.create :owner_id => self.user_id, :project_id => self.project_id, :amount => self.amount #Add as contributor self.user.add_as_contributor_if_new(self.project.root) admin = User.sysadmin Notification.create :recipient_id => self.user_id, :variation => 'credits_distributed', :params => {:project_name => project.name, :credit_distribution => self.attributes, :enterprise_id => self.project.root.id}, :sender_id => admin.id, :source_id => self.id, :source_type => "Credit" end end ================================================ FILE: app/models/credit_transfer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class CreditTransfer < ActiveRecord::Base belongs_to :sender, :class_name => "User", :foreign_key => "sender_id" belongs_to :recipient, :class_name => "User", :foreign_key => "recipient_id" belongs_to :project after_create :send_notification def send_notification Notification.create :recipient_id => self.recipient_id, :variation => 'credits_transferred', :params => {:amount => self.amount, :note => self.note, :project => self.project, :sender_name => sender.name}, :sender_id => self.sender_id, :source_id => self.id, :source_type => "CreditTransfer" end end ================================================ FILE: app/models/daily_digest.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class DailyDigest < ActiveRecord::Base belongs_to :issue belongs_to :journal def self.deliver digests_by_mail = DailyDigest.all.group_by{|digest| digest.mail} digests_by_mail.each_pair do |mail,journals| Mailer.send_later(:deliver_daily_digest,mail,journals) DailyDigest.delete_all :mail => mail end end end ================================================ FILE: app/models/document.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Document < ActiveRecord::Base belongs_to :project acts_as_attachable :delete_permission => :manage_documents acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_at ASC")) ? a.author : nil }, :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} validates_presence_of :project, :title validates_length_of :title, :maximum => 60 def size sum = 0.0 attachments.each do |a| sum += a.filesize end sum = sum / 1000000000 sum.round(3) end def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_documents, project) end def updated_at unless @updated_at a = attachments.find(:first, :order => 'created_at DESC') @updated_at = (a && a.created_at) || created_at end @updated_at end # Returns the mail adresses of users that should be notified def recipients notified = project.notified_users notified.reject! {|user| !visible?(user) || user.pref[:no_emails]} notified.collect(&:mail) end end ================================================ FILE: app/models/document_observer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class DocumentObserver < ActiveRecord::Observer def after_create(document) Mailer.send_later(:deliver_document_added,document) if Setting.notified_events.include?('document_added') end end ================================================ FILE: app/models/email_update.rb ================================================ class EmailUpdate < ActiveRecord::Base belongs_to :user def before_create self.token = Token.generate_token_value end def send_activation Mailer.send_later(:deliver_email_update_activation,self) end def accept self.update_attribute(:activated, true) self.user.update_attribute(:mail, self.mail) end end ================================================ FILE: app/models/enabled_module.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class EnabledModule < ActiveRecord::Base belongs_to :project validates_presence_of :name after_create :module_enabled private # after_create callback used to do things when a module is enabled def module_enabled case name when 'wiki' # Create a wiki with a default start page if project && project.wiki.nil? Wiki.create(:project => project, :start_page => 'Wiki') end end end end ================================================ FILE: app/models/enterprise.rb ================================================ class Enterprise < ActiveRecord::Base has_one :root_project, :class_name => 'Project', :conditions => "parent_id is null" has_many :projects has_many :issues, :through => :projects has_many :members, :through => :projects has_many :users, :through => :members has_many :news, :through => :projects validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 50 validates_length_of :homepage, :maximum => 255 end ================================================ FILE: app/models/enumeration.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Enumeration < ActiveRecord::Base default_scope :order => "#{Enumeration.table_name}.position ASC" belongs_to :project acts_as_list :scope => 'type = \'#{type}\'' acts_as_tree :order => 'position ASC' before_destroy :check_integrity validates_presence_of :name validates_uniqueness_of :name, :scope => [:type, :project_id] validates_length_of :name, :maximum => 30 named_scope :values, lambda {|type| { :conditions => { :type => type }, :order => 'position' } } do def default find(:first, :conditions => { :is_default => true }) end end # End backwards compatiblity named_scopes named_scope :shared, :conditions => { :project_id => nil } named_scope :active, :conditions => { :active => true } def self.default # Creates a fake default scope so Enumeration.default will check # it's type. STI subclasses will automatically add their own # types to the finder. if self.descends_from_active_record? find(:first, :conditions => { :is_default => true, :type => 'Enumeration' }) else # STI classes are find(:first, :conditions => { :is_default => true }) end end # Overloaded on concrete classes def option_name nil end # Backwards compatiblity. Can be removed post-0.9 def opt ActiveSupport::Deprecation.warn("Enumeration#opt is deprecated, use the STI classes now. (#{Redmine::Info.issue(3007)})") return OptName end def before_save if is_default? && is_default_changed? Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type}) end end # Overloaded on concrete classes def objects_count 0 end def in_use? self.objects_count != 0 end # Is this enumeration overiding a system level enumeration? def is_override? !self.parent.nil? end alias :destroy_without_reassign :destroy # Destroy the enumeration # If a enumeration is specified, objects are reassigned def destroy(reassign_to = nil) if reassign_to && reassign_to.is_a?(Enumeration) self.transfer_relations(reassign_to) end destroy_without_reassign end def <=>(enumeration) position <=> enumeration.position end def to_s; name end # Returns the Subclasses of Enumeration. Each Subclass needs to be # required in development mode. # # Note: subclasses is protected in ActiveRecord def self.get_subclasses @@subclasses[Enumeration] end # Does the +new+ Hash override the previous Enumeration? def self.overridding_change?(new, previous) if (same_active_state?(new['active'], previous.active)) return false else return true end end # Are the new and previous fields equal? def self.same_active_state?(new, previous) new = (new == "1" ? true : false) return new == previous end private def check_integrity raise "Can't delete enumeration" if self.in_use? end end ================================================ FILE: app/models/help_section.rb ================================================ class HelpSection < ActiveRecord::Base belongs_to :user end ================================================ FILE: app/models/hourly_type.rb ================================================ class HourlyType < ActiveRecord::Base belongs_to :project has_many :issues validates_presence_of :name, :project, :hourly_rate_per_person, :hourly_cap validates_length_of :name, :maximum => 255 validates_numericality_of :hourly_rate_per_person, :hourly_cap def validate errors.add(:hourly_rate_per_person, "should be at least 1") if hourly_rate_per_person.nil? || hourly_rate_per_person < 1 errors.add(:hourly_cap, "should be at least 1") if hourly_cap.nil? || hourly_cap < 1 end def name_with_rates "#{name} -- rate per person: #{hourly_rate_per_person}, cap: #{hourly_cap}" end end ================================================ FILE: app/models/invitation.rb ================================================ class Invitation < ActiveRecord::Base belongs_to :project belongs_to :user belongs_to :role PENDING = 0 ACCEPTED = 1 def before_create # TODO: check for dupes? self.token = Token.generate_token_value # TODO: should be `self.role ||= Role.contributor` self.role_id = Role.contributor.id unless self.role_id end def deliver(note="") return unless self.status == PENDING Mailer.send(:deliver_invitation_add,self,note) # add notification here # TODO: don't send email, once notifications are auto-sending emails recipient = User.find_by_mail(self.mail) Notification.create :recipient_id => recipient.id, :variation => 'invitation', :params => {:role_name => self.role.name, :project_name => self.project.name, :project_id => self.project_id, :token => self.token, :note => note}, :sender_id => self.user_id, :source_id => self.id, :source_type => "Invitation" if recipient end def resend(note="") return unless self.status == PENDING Mailer.send_later(:deliver_invitation_remind,self,note) return true end def status_name case self.status when PENDING return "Pending" when ACCEPTED return "Accepted" else return "Unkown" end end def accept(user=nil) return unless self.status == PENDING if user && !user.anonymous? @user = user elsif self.new_mail && !self.new_mail.empty? @user = User.find_by_mail(self.new_mail) else @user = User.find_by_mail(self.mail) end return unless @user && !@user.anonymous? if self.project.root? @user.add_to_project self.project, self.role else @user.add_to_project self.project, Role.active @user.add_to_project self.project.root, self.role end @user.add_to_project self.project, Role.clearance unless self.project.is_public? self.new_mail = @user.mail if @user.mail self.status = ACCEPTED self.save! Notification.delete_all(:variation => 'invitation', :source_id => self.id) end end ================================================ FILE: app/models/issue.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Issue < ActiveRecord::Base belongs_to :project belongs_to :tracker belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' belongs_to :retro belongs_to :hourly_type has_many :journals, :as => :journalized, :dependent => :destroy, :order => "#{Journal.table_name}.created_at ASC" has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all has_many :issue_votes, :dependent => :delete_all has_many :todos, :dependent => :delete_all acts_as_attachable :after_remove => :attachment_removed acts_as_watchable acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], :include => [:project, :journals], # sort by id so that limited eager loading doesn't break with postgresql :order_column => "#{table_name}.id" acts_as_taggable acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}, :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') } # =============== # = CSV support = # =============== comma do # implicitly named :default id tracker :name project :name subject description status :name assigned_to :name author :name points pri accept reject agree disagree accept_nonbind reject_nonbind agree_nonbind disagree_nonbind agree_total_nonbind points_nonbind pri_nonbind hourly_type :name num_hours created_at updated_at end DONE_RATIO_OPTIONS = %w(issue_field issue_status) validates_presence_of :subject, :project, :tracker, :author, :status validates_length_of :subject, :maximum => 255 named_scope :visible, lambda {|*args| { :include => :project, :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status named_scope :open_status, :conditions => {:status_id => 1}, :include => :status #BUGBUG: hard coded because IssueStatus.open.id breaks rake for some reason!!! after_save :after_save # Returns true if usr or current user is allowed to view the issue def visible?(usr=nil) (usr || User.current).allowed_to?(:view_issues, self.project) end # Returns true if there are enough agreements in relation to the estimated points of the request def ready_for_open? return false if points.nil? || agree_total < 1 return true if agree - disagree > points_from_credits / 2 return true if agree_total > 0 return false end # Returns true if there are enough disagreements in relation to the estimated points of the request def ready_for_canceled? return false if agree_total > 0 return true if agree_total < 0 && (updated_at < DateTime.now - Setting::LAZY_MAJORITY_LENGTH) return false end def ready_for_accepted? return true if self.status == IssueStatus.accepted return false if points.nil? || accept_total < 1 return true if accept_total > 0 && (self.updated_at < DateTime.now - Setting::LAZY_MAJORITY_LENGTH) return false end def ready_for_rejected? return true if self.status == IssueStatus.rejected return false if points.nil? || accept_total > -1 return true if accept_total < 0 && (updated_at < DateTime.now - Setting::LAZY_MAJORITY_LENGTH) #rejected return false end def is_gift? tracker.gift? end def is_expense? tracker.expense? end def is_hourly? tracker.hourly? end def is_feature tracker.feature? end def is_bug tracker.bug? end def is_chore tracker.chore? end def updated_status return IssueStatus.canceled if self.status == IssueStatus.canceled return IssueStatus.accepted if ready_for_accepted? return IssueStatus.rejected if ready_for_rejected? return IssueStatus.done if self.status == IssueStatus.done || (ready_for_open? && is_gift?) return IssueStatus.inprogress if self.status == IssueStatus.inprogress return IssueStatus.open if ready_for_open? return IssueStatus.canceled if ready_for_canceled? return IssueStatus.newstatus #default end def after_initialize if new_record? # set default values for new records only self.status ||= IssueStatus.default end end # Returns true if one or more people joined this issue def has_team? team_votes.length>1 end def has_todos? todos.length>0 end def team_votes issue_votes.select {|i| i.vote_type == IssueVote::JOIN_VOTE_TYPE} end def team_members IssueVote.find(:all, :conditions => ["issue_id=? AND vote_type=?", self.id, IssueVote::JOIN_VOTE_TYPE], :order => "updated_at ASC").map(&:user) end def copy_from(arg) issue = arg.is_a?(Issue) ? arg : Issue.find(arg) self.attributes = issue.attributes.dup.except("id", "created_at", "updated_at") self.status = issue.status self end #returns true if issue can be started (in the correct priority tier) def startable? return false unless self.status_id == IssueStatus.open.id self.pri > project.issues.open_status.maximum("pri") - Setting::NUMBER_OF_STARTABLE_PRIORITY_TIERS || points_from_credits == 0 end #Creates a new issue and sets its status to open #Copies all issue votes except team ones and accept/reject ones def clone_recurring @new_issue = Issue.new @new_issue.attributes = self.attributes.dup.except("id", "created_at", "updated_at") @new_issue.status = IssueStatus.open @new_issue.save self.issue_votes.each do |iv| next if iv.vote_type == IssueVote::JOIN_VOTE_TYPE || iv.vote_type == IssueVote::ACCEPT_VOTE_TYPE @new_iv = IssueVote.new @new_iv.attributes = iv.attributes.dup.except("id", "issue_id") @new_iv.issue_id = @new_issue.id @new_iv.save end end # Moves/copies an issue to a new project and tracker # Returns the moved/copied issue on success, false on failure def move_to(new_project, new_tracker = nil, options = {}) options ||= {} issue = options[:copy] ? self.clone : self transaction do if new_project && issue.project_id != new_project.id # delete issue relations unless Setting.cross_project_issue_relations? issue.relations_from.clear issue.relations_to.clear end issue.project = new_project end if new_tracker issue.tracker = new_tracker end if options[:copy] issue.status = if options[:attributes] && options[:attributes][:status_id] IssueStatus.find_by_id(options[:attributes][:status_id]) else self.status end end # Allow bulk setting of attributes on the issue if options[:attributes] issue.attributes = options[:attributes] end if !issue.save Issue.connection.rollback_db_transaction return false end end return issue end def tracker_id=(tid) self.tracker = nil write_attribute(:tracker_id, tid) result = write_attribute(:tracker_id, tid) result end def mention(mentioner_id, mentioned_id, mention_text) Notification.create :recipient_id => mentioned_id, :variation => 'mention', :params => {:mention_text => self.description, :url => {:controller => "issues", :action => "show", :id => self.id}, :title => self.subject}, :sender_id => mentioner_id, :source_id => self.id, :source_type => "Issue" end def update_tags(tags) self.update_attribute(:tag_list, tags) self.update_attribute(:tags_copy, self.tags.map {|t|t.name}.join(",")) update_last_item_stamp end # Overrides attributes= so that tracker_id gets assigned first def attributes_with_tracker_first=(new_attributes, *args) return if new_attributes.nil? new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id] if new_tracker_id self.tracker_id = new_tracker_id end self.attributes_without_tracker_first = new_attributes, *args end alias_method_chain :attributes=, :tracker_first def estimated_hours=(h) write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) end def validate end def init_journal(user, notes = "") @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) @issue_before_change = self.clone @issue_before_change.status = self.status # Make sure updated_at is updated when adding a note. updated_at_will_change! @current_journal end # Return true if the issue is closed, otherwise false def closed? self.status.is_closed? end # Return true if the issue is being reopened def reopened? if !new_record? && status_id_changed? status_was = IssueStatus.find_by_id(status_id_was) status_new = IssueStatus.find_by_id(status_id) if status_was && status_new && status_was.is_closed? && !status_new.is_closed? return true end end false end def editable? return !(status == IssueStatus.assigned || status == IssueStatus.done || status == IssueStatus.canceled || status == IssueStatus.archived) end # Returns true if the issue is overdue def overdue? !due_date.nil? && (due_date < DateTime.now) && !status.is_closed? end # Users the issue can be assigned to def assignable_users project.assignable_users end # Returns true if this issue is blocked by another issue that is still open def blocked? !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil? end # Returns an array of status that user is able to apply def new_statuses_allowed_to(user) logger.info { "New statuses allowed" } statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker) statuses << status unless statuses.empty? statuses = statuses.uniq.sort blocked? ? statuses.reject {|s| s.is_closed?} : statuses end # Returns the mail adresses of users that should be notified def recipients notified = project.notified_users # Author and assignee are always notified unless they have been locked notified << author if author && author.active? notified << assigned_to if assigned_to && assigned_to.active? unless self.tracker.gift? notified += team_members notified += journals.collect {|j| j.user} notified.uniq! # Remove users that can not view the issue notified.reject! {|user| !visible?(user) || user.pref[:no_emails]} notified.delete User.sysadmin notified.collect(&:mail) end def relations (relations_from + relations_to).sort end def all_dependent_issues dependencies = [] relations_from.each do |relation| dependencies << relation.issue_to dependencies += relation.issue_to.all_dependent_issues end dependencies end # Returns an array of issues that duplicate this one def duplicates relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from} end # Returns the due date or the target due date if any # Used on gantt chart def due_before due_date end # Returns the time scheduled for this issue. # # Example: # Start Date: 2/26/09, End Date: 3/04/09 # duration => 6 def duration (start_date && due_date) ? due_date - start_date : 0 end def soonest_start @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min end def to_s "#{tracker} ##{id}: #{subject}" end # Returns a string of css classes that apply to the issue def css_classes s = "issue status-#{status.position}" s << ' closed' if closed? s << ' overdue' if overdue? s << ' created-by-me' if User.current.logged? && author_id == User.current.id s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id s end #returns true if this user is allowed to take (and/or offer) ownership for this particular issue def push_allowed?(user) return false if user.nil? return true if self.assigned_to == user #Any user who owns an issue can offer for people to take it, or can accept offers #True if user has push commitment, AND expected date has passed or doesn't exist AND it's assigned to nobody or assigned to same user user.allowed_to?(:push_commitment, self.project) && (self.expected_date.nil? || self.expected_date < Time.new.to_date) && (self.assigned_to.nil? || self.assigned_to == user) end def update_estimate_total(binding) if binding self.points = IssueVote.average(:points, :conditions => " issue_id = #{self.id} AND vote_type = #{IssueVote::ESTIMATE_VOTE_TYPE} AND isbinding= true AND points != -1") else self.points_nonbind = IssueVote.average(:points, :conditions => " issue_id = #{self.id} AND vote_type = #{IssueVote::ESTIMATE_VOTE_TYPE} AND isbinding= false AND points != -1") end end def update_pri_total(binding) if binding self.pri = IssueVote.sum(:points, :conditions => {:issue_id => self.id, :vote_type => IssueVote::PRI_VOTE_TYPE, :isbinding=> true}) else self.pri_nonbind = IssueVote.sum(:points, :conditions => {:issue_id => self.id, :vote_type => IssueVote::PRI_VOTE_TYPE, :isbinding=> false}) end end def update_agree_total(binding) if binding self.agree = IssueVote.count(:conditions => {:issue_id => self.id, :vote_type => IssueVote::AGREE_VOTE_TYPE, :points => 1, :isbinding=> true}) self.disagree = IssueVote.sum(:points, :conditions => "issue_id = '#{self.id}' AND vote_type = '#{IssueVote::AGREE_VOTE_TYPE}' AND points < 0 AND isbinding='true'") * -1 self.agree_total = self.agree - self.disagree else self.agree_nonbind = IssueVote.count(:conditions => {:issue_id => self.id, :vote_type => IssueVote::AGREE_VOTE_TYPE, :points => 1, :isbinding=> false}) self.disagree_nonbind = IssueVote.sum(:points, :conditions => "issue_id = '#{self.id}' AND vote_type = '#{IssueVote::AGREE_VOTE_TYPE}' AND points < 0 AND isbinding='false'") * -1 self.agree_total_nonbind = self.agree_nonbind - self.disagree_nonbind end end def update_accept_total(binding) if binding self.accept = IssueVote.count(:conditions => {:issue_id => self.id, :vote_type => IssueVote::ACCEPT_VOTE_TYPE, :points => 1, :isbinding=> true}) self.reject = IssueVote.sum(:points, :conditions => "issue_id = '#{self.id}' AND vote_type = '#{IssueVote::ACCEPT_VOTE_TYPE}' AND points < 0 AND isbinding='true'") * -1 self.accept_total = self.accept - self.reject else self.accept_nonbind = IssueVote.count(:conditions => {:issue_id => self.id, :vote_type => IssueVote::ACCEPT_VOTE_TYPE, :points => 1, :isbinding=> false}) self.reject_nonbind = IssueVote.sum(:points, :conditions => "issue_id = '#{self.id}' AND vote_type = '#{IssueVote::ACCEPT_VOTE_TYPE}' AND points < 0 AND isbinding='false'") * -1 self.accept_total_nonbind = self.accept_nonbind - self.reject_nonbind end end #refreshes issue status, returns true if status changed def update_status original = self.status @updated = self.updated_status if (original.id != @updated.id) admin = User.find(:first,:conditions => {:login => "admin"}) journal = self.init_journal(admin) self.status = @updated self.retro_id = nil LogActivityStreams.write_single_activity_stream(User.sysadmin,:name,self,:subject,:changed,"update_to_#{@updated.name}", 0, @updated,{ :indirect_object_name_method => :name, :indirect_object_phrase => "From #{original.name} to #{@updated.name} " }) if self.status == IssueStatus.accepted self.assigned_to.add_as_contributor_if_new(self.project) unless self.assigned_to.nil? if self.is_gift? self.retro_id = Retro::NOT_NEEDED_ID self.give_credits elsif self.is_expense? self.retro_id = Retro::NOT_NEEDED_ID self.give_credits elsif self.is_hourly? self.retro_id = Retro::GIVEN_BUT_NOT_PART_OF_RETRO self.give_credits else #if a non-gift/expense/hourly is accepted, set retro id to not started to prep for next retrospective if self.project.credits_enabled? self.retro_id = Retro::NOT_STARTED_ID self.project.start_retro_if_ready else self.retro_id = Retro::NOT_ENABLED_ID #credits not enabled, we don't care end end end self.save return true else return false end end # sets number of points for an hourly item def set_points_from_hourly return unless self.is_hourly? if (hourly_type.hourly_rate_per_person * self.team_members.length) > hourly_type.hourly_cap self.points = hourly_type.hourly_cap * self.num_hours else self.points = self.num_hours * self.team_members.length * hourly_type.hourly_rate_per_person end end # issues credits for this one issue to the people it's assigned to def give_credits if self.is_gift? CreditDistribution.create(:user_id => self.assigned_to_id, :project_id => self.project_id, :retro_id => CreditDistribution::GIFT, :amount => self.points) unless self.points == 0 || self.points.nil? elsif self.is_expense? CreditDistribution.create(:user_id => self.assigned_to_id, :project_id => self.project_id, :retro_id => CreditDistribution::EXPENSE, :amount => self.points) unless self.points == 0 || self.points.nil? elsif self.is_hourly? credits_per_person_per_hour = 0 if (hourly_type.hourly_rate_per_person * self.team_members.length) > hourly_type.hourly_cap credits_per_person_per_hour = hourly_type.hourly_cap / self.team_members.length else credits_per_person_per_hour = hourly_type.hourly_rate_per_person end credits_per_person = credits_per_person_per_hour * self.num_hours self.team_members.each do |member| CreditDistribution.create(:user_id => member.id, :project_id => self.project_id, :retro_id => CreditDistribution::HOURLY, :amount => credits_per_person) unless credits_per_person == 0 || self.points.nil? end end end #returns json object for consumption from dashboard def to_dashboard self.to_json(:include => {:journals => { :only => [:id, :notes, :created_at, :user_id], :include => {:user => { :only => [:firstname, :lastname, :login] }}}, :issue_votes => { :include => {:user => { :only => [:firstname, :lastname, :login] }}}, :status => {:only => :name}, :attachments => { :only => [:id, :filename]}, :todos => {:only => [:id, :subject, :completed_on, :owner_login]}, :tracker => {:only => [:name,:id]}, :author => {:only => [:firstname, :lastname, :login, :mail_hash]}, :assigned_to => { :only => [:firstname, :lastname, :login] } }, :except => :tags) end #returns dollar amount based on points for this issue def dollar_amount return self.points end #returns number of "points" based on scale. used to calculate votes needed in lazy majority def points_from_credits normalized = (points/self.project.dpp).round return Setting::CREDITS_TO_POINTS[Setting::CREDITS_TO_POINTS.length - 1] if normalized > Setting::CREDITS_TO_POINTS.length #returns max if credits are more than max return Setting::CREDITS_TO_POINTS[normalized]; end def size sum = 0.0 attachments.each do |a| sum += a.filesize end sum = sum / 1000000000 sum.round(3) end def after_create self.project.send_later :refresh_issue_count end def update_last_item_stamp self.project.send_later("update_last_item") end private # Callback on attachment deletion def attachment_removed(obj) journal = init_journal(User.current) journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename) journal.save end def after_save # Reload is needed in order to get the right status reload # Update start/due dates of following issues relations_from.each(&:set_issue_to_dates) # Close duplicates if the issue was closed if @issue_before_change && !@issue_before_change.closed? && self.closed? duplicates.each do |duplicate| # Reload is need in case the duplicate was updated by a previous duplicate duplicate.reload # Don't re-close it if it's already closed next if duplicate.closed? # Same user and notes duplicate.init_journal(@current_journal.user, @current_journal.notes) duplicate.update_attribute :status, IssueStatus.archived end end update_last_item_stamp #TODO: should be before save! create_journal end # Saves the changes in a Journal # Called after_save def create_journal if @current_journal logger.info { "creating journal..." } # attributes changes (Issue.column_names - %w(id lock_version created_at updated_at pri accept reject accept_total agree disagree agree_total retro_id accept_nonbind reject_nonbind accept_total_nonbind agree_nonbind disagree_nonbind agree_total_nonbind points_nonbind pri_nonbind tags)).each {|c| @current_journal.details << JournalDetail.new(:property => 'attr', :prop_key => c, :old_value => @issue_before_change.send(c), :value => send(c)) unless send(c)==@issue_before_change.send(c) } @current_journal.save end end end ================================================ FILE: app/models/issue_observer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssueObserver < ActiveRecord::Observer def after_create(issue) Mailer.send_later(:deliver_issue_add,issue) if Setting.notified_events.include?('issue_added') end end ================================================ FILE: app/models/issue_relation.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssueRelation < ActiveRecord::Base belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' TYPE_RELATES = "relates" TYPE_DUPLICATES = "duplicates" TYPE_BLOCKS = "blocks" TYPE_PRECEDES = "precedes" TYPE_FOLLOWS = "follows" TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 }, TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 }, TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 }, TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 }, TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 5 } }.freeze validates_presence_of :issue_from, :issue_to, :relation_type validates_inclusion_of :relation_type, :in => TYPES.keys validates_numericality_of :delay, :allow_nil => true validates_uniqueness_of :issue_to_id, :scope => :issue_from_id attr_protected :issue_from_id, :issue_to_id def validate if issue_from && issue_to errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations? errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from end end def other_issue(issue) (self.issue_from_id == issue.id) ? issue_to : issue_from end def label_for(issue) TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow end def before_save reverse_if_needed if TYPE_PRECEDES == relation_type self.delay ||= 0 else self.delay = nil end set_issue_to_dates end def set_issue_to_dates soonest_start = self.successor_soonest_start if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start) issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration issue_to.save end end def successor_soonest_start return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date) (issue_from.due_date || issue_from.start_date) + 1 + delay end def <=>(relation) TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order] end private def reverse_if_needed if (TYPE_FOLLOWS == relation_type) issue_tmp = issue_to self.issue_to = issue_from self.issue_from = issue_tmp self.relation_type = TYPE_PRECEDES end end end ================================================ FILE: app/models/issue_status.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class IssueStatus < ActiveRecord::Base before_destroy :check_integrity has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all acts_as_list validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 validates_format_of :name, :with => /^[\w\s\'\-]*$/i def after_save IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default? end # Returns the default status for new issues def self.default find(:first, :conditions =>["is_default=?", true]) end def self.assigned @@assigned_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_assigned)]) end def self.done @@done_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_done)]) end def self.inprogress @@inprogress_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_inprogress)]) end def self.newstatus @@newstatus_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_new)]) end def self.open @@open_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_open)]) end def self.canceled @@canceled_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_canceled)]) end def self.estimate @@estimate_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_estimate)]) end def self.accepted find(:first, :conditions =>["name=?", l(:default_issue_status_accepted)]) end def self.rejected find(:first, :conditions =>["name=?", l(:default_issue_status_rejected)]) end def self.archived @@archived_status ||= find(:first, :conditions =>["name=?", l(:default_issue_status_archived)]) end # Returns an array of all statuses the given role can switch to # Uses association cache when called more than one time def new_statuses_allowed_to(roles, tracker) if roles && tracker role_ids = roles.collect(&:id) new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort else [] end end # Same thing as above but uses a database query # More efficient than the previous method if called just once def find_new_statuses_allowed_to(roles, tracker) if roles && tracker workflows.find(:all, :include => :new_status, :conditions => { :role_id => roles.collect(&:id), :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort else [] end end def new_status_allowed_to?(status, roles, tracker) if status && roles && tracker !workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil? else false end end def <=>(status) position <=> status.position end def to_s; name end private def check_integrity raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id]) end end ================================================ FILE: app/models/issue_vote.rb ================================================ class IssueVote < ActiveRecord::Base belongs_to :user belongs_to :issue before_create :remove_similar before_save :set_binding AGREE_VOTE_TYPE = 1 ACCEPT_VOTE_TYPE = 2 PRI_VOTE_TYPE = 3 ESTIMATE_VOTE_TYPE = 4 JOIN_VOTE_TYPE = 5 # Not exactly a vote, but used to join the team that's working on an issue def project issue.project end def update_issue_totals case vote_type when AGREE_VOTE_TYPE issue.update_agree_total self.isbinding when ACCEPT_VOTE_TYPE issue.update_accept_total self.isbinding when PRI_VOTE_TYPE issue.update_pri_total self.isbinding when ESTIMATE_VOTE_TYPE issue.update_estimate_total self.isbinding end end def remove_similar deleted = IssueVote.delete_all(:issue_id => issue_id, :user_id => user_id, :vote_type => vote_type) #log activity for estimate change if deleted > 0 && vote_type == ESTIMATE_VOTE_TYPE self.issue.save LogActivityStreams.write_single_activity_stream(self.user,:name,self.issue,:subject,"changed their estimate for",:issues, 0, nil,{}) end end def set_binding result = User.find(self.user_id).binding_voter_of?(self.issue.project) self.isbinding = result return true end end ================================================ FILE: app/models/journal.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Journal < ActiveRecord::Base belongs_to :journalized, :polymorphic => true # added as a quick fix to allow eager loading of the polymorphic association # since always associated to an issue, for now belongs_to :issue, :foreign_key => :journalized_id belongs_to :user has_many :details, :class_name => "JournalDetail", :dependent => :delete_all attr_accessor :indice after_save :update_issue_timestamp, :send_mentions, :parse_relations def update_issue_timestamp issue.updated_at = DateTime.now issue.save end def send_mentions Mention.parse(self, self.user_id) end def mention(mentioner_id, mentioned_id, mention_text) Notification.create :recipient_id => mentioned_id, :variation => 'mention', :params => {:mention_text => self.notes, :url => {:controller => self.journalized_type.to_s.pluralize.downcase, :action => "show", :id => self.journalized_id}, :title => self.issue.subject}, :sender_id => mentioner_id, :source_id => self.id, :source_type => "Journal(#{self.journalized_type})" end def parse_relations self.send_later(:parse_relations_delayed) end def issue_id self.journalized_id end #parses issue ids in body of journal, and adds related issues def parse_relations_delayed text = self.notes return if text.nil? text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(@)([a-zA-Z0-9._@]+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m| leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8 link = nil if esc.nil? if sep == '#' @relation = IssueRelation.new @relation.issue_from_id = self.journalized_id @relation.issue_to_id = oid @relation.relation_type = IssueRelation::TYPE_RELATES @relation.save end end end end def save(*args) # Do not save an empty journal (details.empty? && notes.blank?) ? false : super end # Returns the new status if the journal contains a status change, otherwise nil def new_status c = details.detect {|detail| detail.prop_key == 'status_id'} (c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil end def new_value_for(prop) c = details.detect {|detail| detail.prop_key == prop} c ? c.value : nil end def editable_by?(usr) usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) end def project journalized.respond_to?(:project) ? journalized.project : nil end def attachments journalized.respond_to?(:attachments) ? journalized.attachments : nil end end ================================================ FILE: app/models/journal_detail.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class JournalDetail < ActiveRecord::Base belongs_to :journal def before_save self.value = value[0..254] if value && value.is_a?(String) self.old_value = old_value[0..254] if old_value && old_value.is_a?(String) end end ================================================ FILE: app/models/journal_observer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class JournalObserver < ActiveRecord::Observer def after_create(journal) if journal.user == User.sysadmin #send system events to a daily digest all_recipients = journal.issue.recipients + (journal.issue.watcher_recipients - journal.issue.recipients) all_recipients.each do |rec| DailyDigest.create :mail => rec, :journal_id => journal.id, :issue_id => journal.issue.id end else Mailer.send_later(:deliver_issue_edit,journal) if Setting.notified_events.include?('issue_updated') end end end ================================================ FILE: app/models/mail.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Mail < ActiveRecord::Base is_private_message # The :to accessor is used by the scaffolding, # uncomment it if using it or you can remove it if not attr_accessor :to end ================================================ FILE: app/models/mail_handler.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class MailHandler < ActiveRecord::Base include ActionView::Helpers class UnauthorizedAction < StandardError; end class MissingInformation < StandardError; end attr_reader :email, :user # BUGBUG: this initialize won't work consistently # when extending from ActiveRecord initialize doesn't always get called # http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html # better to make this an after_initialize def initialize(email, user,options = {}) logger.info { "initializing mail handler" } if logger @@handler_options = options.dup @@handler_options[:issue] ||= {} @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String) @@handler_options[:allow_override] ||= [] # Project needs to be overridable if not specified @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) # Status overridable by default @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) @user = user @email = email dispatch end def self.receive(email, options={}) @@handler_options = options.dup @@handler_options[:issue] ||= {} @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String) @@handler_options[:allow_override] ||= [] # Project needs to be overridable if not specified @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) # Status overridable by default @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) super email end # Processes incoming emails # Returns the created object (eg. an issue, a message) or false def receive(email) @email = email sender_email = email.from.to_a.first.to_s.strip # Ignore emails received from the application emission address to avoid hell cycles if sender_email.downcase == Setting.mail_from.to_s.strip.downcase return false end @user = User.find_by_mail(sender_email) if @user && !@user.active? return false end if @user.nil? # Email was submitted by an unknown user case @@handler_options[:unknown_user] when 'accept' @user = User.anonymous when 'create' @user = MailHandler.create_user_from_email(email) if @user Mailer.deliver_account_information(@user, @user.password) else logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error return false end else # Default behaviour, emails from unknown users are ignored return false end end User.current = @user dispatch end # Processes incoming emails # Returns the created object (eg. an issue, a message) or false def self.receive_from_api(email,options ={}) logger.info { "recieving from api" } if logger @@handler_options = options.dup @email = email logger.info { "1" } if logger sender_email = email.from.to_a.first.to_s.strip # Ignore emails received from the application emission address to avoid hell cycles if sender_email.downcase == Setting.mail_from.to_s.strip.downcase return false end logger.info { "2" } if logger @user = User.find_by_mail(sender_email) if @user && !@user.active? return false end logger.info { "3" } if logger if @user.nil? # Email was submitted by an unknown user case @@handler_options[:unknown_user] when 'accept' @user = User.anonymous when 'create' @user = MailHandler.create_user_from_email(email) if @user Mailer.deliver_account_information(@user, @user.password) else return false end else # Default behaviour, emails from unknown users are ignored return false end end logger.info { "4" } if logger mailhandler = MailHandler.new(email, @user) end private MESSAGE_ID_RE = %r{^ e # TODO: send a email to the user logger.error e.message if logger false rescue MissingInformation => e logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger false rescue UnauthorizedAction => e logger.error "MailHandler: unauthorized attempt from #{user}" if logger false end # Creates a new issue def receive_issue project = target_project tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first) status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status))) # check permission unless @@handler_options[:no_permission_check] raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) end issue = Issue.new(:author => user, :project => project, :tracker => tracker) # check workflow if status && issue.new_statuses_allowed_to(user).include?(status) issue.status = status end issue.subject = email.subject.chomp issue.subject = issue.subject.toutf8 if issue.subject.respond_to?(:toutf8) if issue.subject.blank? issue.subject = '(no subject)' end issue.description = cleaned_up_text_body # add To and Cc as watchers before saving so the watchers can reply to Redmine add_watchers(issue) issue.save! add_attachments(issue) issue end def target_project # TODO: other ways to specify project: # * parse the email To field # * specific project (eg. Setting.mail_handler_target_project) target = Project.find_by_identifier(get_keyword(:project)) raise MissingInformation.new('Unable to determine target project') if target.nil? target end # Adds a note to an existing issue def receive_issue_reply(issue_id) status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status))) issue = Issue.find_by_id(issue_id) return unless issue # check permission unless @@handler_options[:no_permission_check] raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project) raise UnauthorizedAction unless status.nil? || user.allowed_to?(:edit_issues, issue.project) end # add the note journal = issue.init_journal(user, cleaned_up_text_body) add_attachments(issue) # check workflow if status && issue.new_statuses_allowed_to(user).include?(status) issue.status = status end issue.save! LogActivityStreams.write_single_activity_stream(user,:name,issue,:subject,:updated,:issues, 0, journal,{ :indirect_object_description_method => :notes, :indirect_object_phrase => 'GENERATEDETAILS' }) journal end # Reply will be added to the issue def receive_journal_reply(journal_id) journal = Journal.find_by_id(journal_id) if journal && journal.journalized_type == 'Issue' receive_issue_reply(journal.journalized_id) end end # Receives a reply to a forum message def receive_message_reply(message_id) message = Message.find_by_id(message_id) if message message = message.root unless @@handler_options[:no_permission_check] raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) end if !message.locked? reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip, :content => cleaned_up_text_body) reply.author = user reply.board = message.board message.children << reply add_attachments(reply) reply else end end end def add_attachments(obj) if email.has_attachments? email.attachments.each do |attachment| Attachment.create(:container => obj, :file => attachment, :author => user, :content_type => attachment.content_type) end end end # Adds To and Cc as watchers of the given object if the sender has the # appropriate permission def add_watchers(obj) if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project) addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase} unless addresses.empty? watchers = User.active.find(:all, :conditions => ['LOWER(mail) IN (?)', addresses]) watchers.each {|w| obj.add_watcher(w)} end end end def get_keyword(attr, options={}) @keywords ||= {} if @keywords.has_key?(attr) @keywords[attr] else @keywords[attr] = begin if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '') $1.strip elsif !@@handler_options[:issue][attr].blank? @@handler_options[:issue][attr] end end end end # Returns the text/plain part of the email # If not found (eg. HTML-only email), returns the body with tags removed def plain_text_body return @plain_text_body unless @plain_text_body.nil? parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten if parts.empty? parts << @email end plain_text_part = parts.detect {|p| p.content_type == 'text/plain'} if plain_text_part.nil? # no text/plain part found, assuming html-only email # strip html tags and remove doctype directive @plain_text_body = @email.body.to_s @plain_text_body.gsub! /On.{30,100}wrote:(.+)/sm, '' #removing all lines including and after a "On xxxxxx (email)" @plain_text_body = strip_tags(sanitize(@plain_text_body)) @plain_text_body.gsub! %r{^ h, :protocol => Setting.protocol } end # Builds a tmail object used to email recipients of the added issue. # # Example: # issue_add(issue) => tmail object # Mailer.deliver_issue_add(issue) => sends an email to issue recipients def issue_add(issue) redmine_headers 'Project' => issue.project.identifier, 'Issue-Id' => issue.id, 'Issue-Author' => issue.author.login redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to message_id issue recipient_list = issue.recipients recipient_list.delete issue.author.mail if issue.author.pref[:no_self_notified] recipients recipient_list cc(issue.watcher_recipients - @recipients) subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" body :issue => issue, :issue_url => url_for(:controller => 'projects', :action => 'dashboard', :show_issue_id => issue.id) render_multipart('issue_add', body) end # Builds a tmail object used to email recipients of the edited issue. # # Example: # issue_edit(journal) => tmail object # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients def issue_edit(journal) issue = journal.journalized.reload redmine_headers 'Project' => issue.project.identifier, 'Issue-Id' => issue.id, 'Issue-Author' => issue.author.login redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to message_id journal references issue @author = journal.user recipient_list = issue.recipients recipient_list.delete @author.mail if @author.pref[:no_self_notified] recipients recipient_list # Watchers in cc cc(issue.watcher_recipients - @recipients) s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s << "(#{issue.status.name}) " if journal.new_value_for('status_id') s << issue.subject subject s body :issue => issue, :journal => journal, :issue_url => url_for(:controller => 'projects', :action => 'dashboard', :show_issue_id => issue.id) render_multipart('issue_edit', body) end def daily_digest(recipient,journals) @journals = journals @author = User.sysadmin recipients recipient s = "Daily Digest - " s << Time.now.strftime("%m/%d/%Y") subject s body :journals => journals render_multipart('daily_digest', body) end def invitation_add(invitation,note) from invitation.user.mail subject "Invitation to join #{invitation.project.name}" recipients invitation.mail body :user => invitation.user, :project => invitation.project, :note => note, :invitation_url => url_for(:controller => :invitations, :action => "accept", :id => invitation.id, :token => invitation.token), :footer => Setting.emails_footer_nospam, :ignore_bcc_setting => true render_multipart('invitation_add', body) @ignore_bcc_setting = true end def invitation_remind(invitation,note) from invitation.user.mail subject "Reminder: Invitation to join #{invitation.project.name}" recipients invitation.mail body :user => invitation.user, :project => invitation.project, :note => note, :invitation_url => url_for(:controller => :invitations, :action => "accept", :id => invitation.id, :token => invitation.token), :footer => Setting.emails_footer_nospam, :ignore_bcc_setting => true render_multipart('invitation_remind', body) @ignore_bcc_setting = true end def reminder(user, issues, days) set_language_if_valid user.language recipients user.mail subject l(:mail_subject_reminder, issues.size) body :issues => issues, :days => days, :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc') render_multipart('reminder', body) end # Builds a tmail object used to email users belonging to the added document's project. # # Example: # document_added(document) => tmail object # Mailer.deliver_document_added(document) => sends an email to the document's project recipients def document_added(document) redmine_headers 'Project' => document.project.identifier recipients document.recipients subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" body :document => document, :document_url => url_for(:controller => 'documents', :action => 'show', :id => document) render_multipart('document_added', body) end # Builds a tmail object used to email recipients of a project when an attachements are added. # # Example: # attachments_added(attachments) => tmail object # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients def attachments_added(attachments) container = attachments.first.container added_to = '' added_to_url = '' case container.class.name when 'Project' added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container) added_to = "#{l(:label_project)}: #{container}" recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project) && !user.pref[:no_emails]} when 'Document' added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) added_to = "#{l(:label_document)}: #{container.title}" recipients container.recipients end redmine_headers 'Project' => container.project.identifier subject "[#{container.project.name}] #{l(:label_attachment_new)}" body :attachments => attachments, :added_to => added_to, :added_to_url => added_to_url render_multipart('attachments_added', body) end # Builds a tmail object used to email recipients of a news' project when a news item is added. # # Example: # news_added(news) => tmail object # Mailer.deliver_news_added(news) => sends an email to the news' project recipients def news_added(news) redmine_headers 'Project' => news.project.identifier message_id news recipients news.recipients subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" body :news => news, :news_url => url_for(:controller => 'news', :action => 'show', :id => news) render_multipart('news_added', body) end # Builds a tmail object used to email the recipients of the specified message that was posted. # # Example: # message_posted(message) => tmail object # Mailer.deliver_message_posted(message) => sends an email to the recipients def message_posted(message) redmine_headers 'Project' => message.project.identifier, 'Topic-Id' => (message.parent_id || message.id) message_id message references message.parent unless message.parent.nil? recipient_list = message.recipients recipient_list.delete message.author.mail if message.author.pref[:no_self_notified] recipients recipient_list all_recipients = (message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients all_recipients.delete(message.author.mail) if message.author.pref[:no_self_notified] || message.author.pref[:no_emails] cc(all_recipients) subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" body :message => message, :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root) render_multipart('message_posted', body) end # Builds a tmail object used to email the recipients of a project of the specified wiki content was added. # # Example: # wiki_content_added(wiki_content) => tmail object # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients def wiki_content_added(wiki_content) redmine_headers 'Project' => wiki_content.project.identifier, 'Wiki-Page-Id' => wiki_content.page.id message_id wiki_content recipient_list = wiki_content.recipients recipient_list.delete wiki_content.author.mail if wiki_content.author.pref[:no_self_notified] recipients recipient_list cc(wiki_content.page.wiki.watcher_recipients - recipients) subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :page => wiki_content.page.pretty_title)}" body :wiki_content => wiki_content, :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title) render_multipart('wiki_content_added', body) end # Builds a tmail object used to email the recipients of a project of the specified wiki content was updated. # # Example: # wiki_content_updated(wiki_content) => tmail object # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients def wiki_content_updated(wiki_content) redmine_headers 'Project' => wiki_content.project.identifier, 'Wiki-Page-Id' => wiki_content.page.id message_id wiki_content recipients wiki_content.recipients cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients) subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :page => wiki_content.page.pretty_title)}" body :wiki_content => wiki_content, :wiki_content_url => url_for(:controller => 'wiki', :action => 'index', :id => wiki_content.project, :page => wiki_content.page.title), :wiki_diff_url => url_for(:controller => 'wiki', :action => 'diff', :id => wiki_content.project, :page => wiki_content.page.title, :version => wiki_content.version) render_multipart('wiki_content_updated', body) end # Builds a tmail object used to email the specified user their account information. # # Example: # account_information(user, password) => tmail object # Mailer.deliver_account_information(user, password) => sends account information to the user def account_information(user, password) set_language_if_valid user.language recipients user.mail subject l(:mail_subject_register, Setting.app_title) body :user => user, :password => password, :login_url => url_for(:controller => 'account', :action => 'login') render_multipart('account_information', body) end # Builds a tmail object used to email all active administrators of an account activation request. # # Example: # account_activation_request(user) => tmail object # Mailer.deliver_account_activation_request(user)=> sends an email to all active administrators def account_activation_request(user) # Send the email to all active administrators recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact subject l(:mail_subject_account_activation_request, Setting.app_title) body :user => user, :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_at', :sort_order => 'desc') render_multipart('account_activation_request', body) end # Builds a tmail object used to email the specified user that their account was activated by an administrator. # # Example: # account_activated(user) => tmail object # Mailer.deliver_account_activated(user) => sends an email to the registered user def account_activated(user) set_language_if_valid user.language recipients user.mail subject l(:mail_subject_register, Setting.app_title) body :user => user, :login_url => url_for(:controller => 'account', :action => 'login') render_multipart('account_activated', body) end def lost_password(token) set_language_if_valid(token.user.language) recipients token.user.mail subject l(:mail_subject_lost_password, Setting.app_title) body :token => token, :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value) render_multipart('lost_password', body) end def register(token) recipients token.user.mail subject l(:mail_subject_register, Setting.app_title) body :token => token, :url => url_for(:controller => 'account', :action => 'activate', :token => token.value) render_multipart('register', body) end # Builds a tmail object used to email recipients of the added issue. # # Example: # issue_add(issue) => tmail object # Mailer.deliver_issue_add(issue) => sends an email to issue recipients def personal_welcome(user,project) from "shereef@bettermeans.com" cc "support@bettermeans.com" recipients user.mail subject "bettermeans and " + project.name body :name => user.firstname, :footer => "Don't ask yourself what the world needs; ask yourself what makes you come alive and then go do that. Because what the world needs is people who are alive." render_multipart('personal_welcome', body) end def test(user) set_language_if_valid(user.language) recipients user.mail subject 'Bettermeans test' body :url => url_for(:controller => 'welcome') render_multipart('test', body) end # Overrides default deliver! method to prevent from sending an email # with no recipient, cc or bcc def deliver!(mail = @mail) return false if (recipients.nil? || recipients.empty?) && (cc.nil? || cc.empty?) && (bcc.nil? || bcc.empty?) # Set Message-Id and References if @message_id_object mail.message_id = self.class.message_id_for(@message_id_object) end if @references_objects mail.references = @references_objects.collect {|o| self.class.message_id_for(o)} end super(mail) end def email_update_activation(email_update) recipients email_update.mail subject l(:mail_subject_email_update_activation, Setting.app_title) body :activation_url => url_for(:controller => 'email_updates', :action => 'activate', :token => email_update.token) render_multipart('email_update_activation', body) end # Sends reminders to issue assignees # Available options: # * :days => how many days in the future to remind about (defaults to 7) # * :tracker => id of tracker for filtering issues (defaults to all trackers) # * :project => id or identifier of project to process (defaults to all projects) def self.reminders(options={}) days = options[:days] || 7 project = options[:project] ? Project.find(options[:project]) : nil tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date] s << "#{Issue.table_name}.assigned_to_id IS NOT NULL" s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}" s << "#{Issue.table_name}.project_id = #{project.id}" if project s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker], :conditions => s.conditions ).group_by(&:assigned_to) issues_by_assignee.each do |assignee, issues| deliver_reminder(assignee, issues, days) unless assignee.nil? end end private def initialize_defaults(method_name) super set_language_if_valid Setting.default_language from Setting.mail_from # Common headers headers 'X-Mailer' => 'BetterMeans', 'X-BetterMeans-Host' => Setting.host_name, 'X-BetterMeans-Site' => Setting.app_title, 'Precedence' => 'bulk', 'Auto-Submitted' => 'auto-generated' end # Appends a Redmine header field (name is prepended with 'X-Redmine-') def redmine_headers(h) h.each { |k,v| headers["X-BetterMeans-#{k}"] = v } end # Overrides the create_mail method def create_mail # Removes the current user from the recipients and cc # if he doesn't want to receive notifications about what he does @author ||= User.current if @author.pref[:no_self_notified] || @author.pref[:no_emails] recipients.delete(@author.mail) if recipients cc.delete(@author.mail) if cc bcc.delete(@author.mail) if bcc end unless @author == User.anonymous #no need to check for prefs if current user is anonymous # Blind carbon copy recipients if Setting.bcc_recipients? bcc([recipients, cc].flatten.compact.uniq) recipients [] cc [] end unless @ignore_bcc_setting super end # Rails 2.3 has problems rendering implicit multipart messages with # layouts so this method will wrap an multipart messages with # explicit parts. # # https://rails.lighthouseapp.com/projects/8994/tickets/2338-actionmailer-mailer-views-and-content-type # https://rails.lighthouseapp.com/projects/8994/tickets/1799-actionmailer-doesnt-set-template_format-when-rendering-layouts def render_multipart(method_name, body) if Setting.plain_text_mail? content_type "text/plain" body render(:file => "#{method_name}.text.plain.html.erb", :body => body, :layout => 'mailer.text.plain.erb') else content_type "multipart/alternative" part :content_type => "text/plain", :body => render(:file => "#{method_name}.text.plain.html.erb", :body => body, :layout => 'mailer.text.plain.erb') part :content_type => "text/html", :body => render_message("#{method_name}.text.html.html.erb", body) end end # Makes partial rendering work with Rails 1.2 (retro-compatibility) def self.controller_path '' end unless respond_to?('controller_path') # Returns a predictable Message-Id for the given object def self.message_id_for(object) # id + timestamp should reduce the odds of a collision # as far as we don't send multiple emails for the same object timestamp = object.send(object.respond_to?(:created_at) ? :created_at : :updated_at) hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.strftime("%Y%m%d%H%M%S")}" host = Setting.mail_from.to_s.gsub(%r{^.*@}, '') host = "#{::Socket.gethostname}.redmine" if host.empty? "<#{hash}@#{host}>" end def message_id(object) @message_id_object = object end def references(object) @references_objects ||= [] @references_objects << object end end # Patch TMail so that message_id is not overwritten module TMail class Mail def add_message_id( fqdn = nil ) self.message_id ||= ::TMail::new_message_id(fqdn) end end end ================================================ FILE: app/models/member.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class Member < ActiveRecord::Base belongs_to :user has_many :member_roles, :dependent => :destroy has_many :roles, :through => :member_roles belongs_to :project validates_presence_of :user, :project validates_uniqueness_of :user_id, :scope => :project_id after_destroy :unwatch_from_permission_change def name self.user.name if self.user end def name_and_id "#{self.user.id.to_s}:#{self.user.name}" end alias :base_role_ids= :role_ids= def role_ids=(arg) arg = [arg] unless arg.respond_to? :collect ids = (arg || []).collect(&:to_i) - [0] # Keep inherited roles ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id) new_role_ids = ids - role_ids # Add new roles new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) } # Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy) member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)} if member_roles_to_destroy.any? member_roles_to_destroy.each(&:destroy) unwatch_from_permission_change end end def <=>(member) a, b = roles.sort.first, member.roles.sort.first a == b ? (user <=> member.user) : (a <=> b) end def deletable? member_roles.detect {|mr| mr.inherited_from}.nil? end protected def validate errors.add_to_base "Role can't be blank" if member_roles.empty? && roles.empty? end private # Unwatch things that the user is no longer allowed to view inside project def unwatch_from_permission_change if user Watcher.prune(:user => user, :project => project) end end end ================================================ FILE: app/models/member_role.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class MemberRole < ActiveRecord::Base belongs_to :member belongs_to :role after_create :send_notification, :log_activity , :refresh_memberships after_destroy :remove_member_if_empty, :refresh_memberships validates_presence_of :role acts_as_event :title => :event_title, :description => :long_description, :author => :user, :type => 'member-role', :url => Proc.new {|o| {:controller => 'projects', :action => 'team', :id => o.member.project_id}} def project member.respond_to?(:project) ? member.project : nil end #used for activity stream def name role.name end def project_id project.id end def user_id member.user.id end def user member.user end def long_description "#{user.name} is now: #{role.name}" end def event_title "Team change" end private def remove_member_if_empty return unless member if member.roles.empty? member.destroy end# unless role_id == Role::BUILTIN_CORE_MEMBER #We don't destory the member if the role being removed is the core_member role since we're going to add a contributor role end def send_notification Notification.create :recipient_id => self.member.user_id, :variation => 'new_role', :params => {:role_name => self.role.name, :project_name => self.member.project.root.name, :enterprise_id => self.member.project.root.id}, :sender_id => User.sysadmin.id, :source_type => "MemberRole", :source_id => self.id if self.role.level == Role::LEVEL_ENTERPRISE end def log_activity return if role.active? || role.clearance? #don't log active memberships LogActivityStreams.write_single_activity_stream(User.sysadmin,:name,self,:name,:created,:memberships, 0, self.member.user,{:indirect_object_phrase => self.member.user.name}) end #refreshes memberships for all private workstreams def refresh_memberships return unless member return unless role.level == Role::LEVEL_ENTERPRISE MemberRole.send_later(:refresh_memberships_delayed,self) end #refreshes memberships for all private workstreams def self.refresh_memberships_delayed(member_role) return unless member_role.member return unless member_role.role.level == Role::LEVEL_ENTERPRISE member_role.member.project.root.descendants.each(&:refresh_active_members) if member_role.member.project end end ================================================ FILE: app/models/message.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Message < ActiveRecord::Base belongs_to :board belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_at ASC" acts_as_attachable belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' acts_as_searchable :columns => ['subject', 'content'], :include => {:board => :project}, :project_key => 'project_id', :date_column => "#{table_name}.created_at" acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, :description => :content, :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :anchor => "message-#{o.id}"})} acts_as_watchable attr_protected :locked, :sticky validates_presence_of :board, :subject, :content validates_length_of :subject, :maximum => 255 after_create :add_author_as_watcher after_save :send_mentions def project_id board.project.id end def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_messages, project) end def validate_on_create # Can not reply to a locked topic errors.add_to_base 'Topic is locked' if root.locked? && self != root end def after_create if parent parent.reload.update_attribute(:last_reply_id, self.id) end board.reset_counters! end def send_mentions Mention.parse(self, self.author_id) end def mention(mentioner_id, mentioned_id, mention_text) Notification.create :recipient_id => mentioned_id, :variation => 'mention', :params => {:mention_text => self.content, :url => {:controller => "messages", :action => "show", :board_id => self.board_id, :id => self.id}, :title => self.subject}, :sender_id => mentioner_id, :source_id => self.id, :source_type => "Message" end def after_update if board_id_changed? Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id]) Board.reset_counters!(board_id_was) Board.reset_counters!(board_id) end end def after_destroy board.reset_counters! end def sticky=(arg) write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) end def sticky? sticky == 1 end def project board.project end def editable_by?(usr) usr && usr.logged? && (usr.allowed_to?(:edit_messages, project) || (self.author == usr && usr.allowed_to?(:edit_own_messages, project))) end def destroyable_by?(usr) usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project))) end # Returns the mail adresses of users that should be notified def recipients notified = project.notified_users notified << author if author && author.active? && !author.pref[:no_emails] notified.reject! {|user| !visible?(user) || user.pref[:no_emails]} notified.collect(&:mail) end private def add_author_as_watcher Watcher.create(:watchable => self.root, :user => author) end end ================================================ FILE: app/models/message_observer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class MessageObserver < ActiveRecord::Observer def after_create(message) Mailer.send_later(:deliver_message_posted,message) if Setting.notified_events.include?('message_posted') end end ================================================ FILE: app/models/motion.rb ================================================ class Motion < ActiveRecord::Base include ActionController::UrlWriter STATE_ACTIVE = 0 STATE_PASSED = 1 STATE_DEFEATED = 2 STATE_CANCELED = 3 TYPE_CONSENSUS = 1 #Any disagree defeats the motion TYPE_MAJORITY = 2 #Any block defeats the motion TYPE_SHARE = 3 #Majority vote, 1 share = 1 vote VISIBLE_BOARD = 1 #Only board can see this motion VISIBLE_CORE = 2 #Only core & board VISIBLE_MEMBER = 3 #All members, core and board VISIBLE_CONTRIBUTER = 4 #Everyone who is a part of the enterprise VISIBLE_USER = 10 #Everyone on the platform BINDING_BOARD = 1 #Only board votes are binding BINDING_CORE = 2 #Only core & board votes are binding BINDING_MEMBER = 3 #All members, core and board votes are binding BINDING_CONTRIBUTER = 4 #Everyone who is a part of the enterprise has a binding vote BINDING_USER = 5 #Everyone on the platform has a binding vote VARIATION_GENERAL = 0 #Miscellaneous issues VARIATION_EXTRAORDINARY = 1 #e.g. sell a company! VARIATION_NEW_MEMBER = 2 VARIATION_NEW_CORE = 3 VARIATION_FIRE_MEMBER = 4 VARIATION_FIRE_CORE = 5 VARIATION_BOARD_PUBLIC = 6 VARIATION_BOARD_PRIVATE = 7 VARIATION_HOURLY_TYPE = 8 serialize :params belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :concerned_user, :class_name => 'User', :foreign_key => 'concerned_user_id' belongs_to :project has_many :motion_votes belongs_to :topic, :class_name => 'Message', :foreign_key => 'topic_id' named_scope :allactive, :conditions => ["state = #{STATE_ACTIVE}", Time.new.to_date] named_scope :viewable_by, lambda { |*level| {:conditions => "visibility_level >= #{level}", :order => "updated_at DESC"} } before_create :set_values after_create :send_announce,:create_forum_topic after_save :close def active? self.state == STATE_ACTIVE end def ended? return true if Time.now > self.ends_on case self.motion_type when TYPE_CONSENSUS #if we don't have a disagreement, and have more than half binding agreement, we're a go if self.disagree == 0 && self.agree_total > self.project.role_and_above_count(self.binding_level).to_f / 2 return true; end when TYPE_MAJORITY #if have majority binding agreement, and no blocking, we're a go if self.disagree < 500 && self.agree_total > self.project.role_and_above_count(self.binding_level).to_f / 2 return true; end end return false end # true if variation requires a concerned user to be specified def concerns_someone? return (self.variation == VARIATION_NEW_MEMBER || self.variation == VARIATION_NEW_CORE || self.variation == VARIATION_FIRE_MEMBER || self.variation == VARIATION_FIRE_CORE) end #Checks if motion has reached end date, calculates vote and takes action def close return if !active? return if !ended? return if self.motion_votes.nil? case self.motion_type when TYPE_CONSENSUS if self.disagree > 0 self.state = STATE_DEFEATED else self.state = STATE_PASSED end when TYPE_MAJORITY if self.disagree > 500 || self.agree_total < 1 self.state = STATE_DEFEATED else self.state = STATE_PASSED end when TYPE_SHARE if (self.agree + (self.disagree * -1)) * Setting::SHARE_MAJORIY_MOTION_RATIO < self.agree self.state = STATE_DEFEATED else self.state = STATE_PASSED end end self.ends_on = DateTime.now self.save announce_passed if self.state == STATE_PASSED execute_action if self.state == STATE_PASSED end def set_values self.title = Setting::MOTIONS[self.variation]["Title"] self.title = self.generate_title self.binding_level = Setting::MOTIONS[self.variation]["Binding"] self.visibility_level = Setting::MOTIONS[self.variation]["Visible"] self.motion_type = Setting::MOTIONS[self.variation]["Type"] self.ends_on = Time.new.advance :days => Setting::MOTIONS[self.variation]["Days"].to_f self.state = STATE_ACTIVE self.author = User.sysadmin if self.author.nil? self.description = self.generate_description end def generate_title case self.variation when VARIATION_GENERAL self.title when VARIATION_EXTRAORDINARY self.title when VARIATION_NEW_MEMBER l(:title_member_nomination, :user => self.concerned_user.name) when VARIATION_NEW_CORE l(:title_core_member_nomination, :user => self.concerned_user.name) when VARIATION_FIRE_MEMBER l(:title_member_removal, :user => self.concerned_user.name) when VARIATION_FIRE_CORE l(:title_core_member_removal, :user => self.concerned_user.name) when VARIATION_BOARD_PUBLIC self.title when VARIATION_BOARD_PRIVATE self.title when VARIATION_HOURLY_TYPE self.title end end def generate_description content = "" case self.variation when VARIATION_GENERAL self.description == "" ? self.title : self.description when VARIATION_EXTRAORDINARY self.description == "" ? self.title : self.description when VARIATION_NEW_MEMBER content << l(:text_member_nomination, :user => self.concerned_user.name, :enterprise => self.project.name) when VARIATION_NEW_CORE content << l(:text_core_member_nomination, :user => self.concerned_user.name, :enterprise => self.project.name) when VARIATION_FIRE_MEMBER content << l(:text_member_removal, :user => self.concerned_user.name, :enterprise => self.project.name) when VARIATION_FIRE_CORE content << l(:text_core_member_removal, :user => self.concerned_user.name, :enterprise => self.project.name) when VARIATION_BOARD_PUBLIC self.description == "" ? self.title : self.description when VARIATION_BOARD_PRIVATE self.description == "" ? self.title : self.description when VARIATION_HOURLY_TYPE self.description == "" ? self.title : self.description end end def self.eligible_users(variation,project_id) project = Project.find(project_id) @concerned_user_list = "" case variation when VARIATION_NEW_MEMBER @concerned_user_list = project.contributor_list when VARIATION_NEW_CORE @concerned_user_list = project.member_list when VARIATION_FIRE_MEMBER @concerned_user_list = project.member_list when VARIATION_FIRE_CORE @concerned_user_list = project.core_member_list end @concerned_user_list end def create_forum_topic main_board = Board.first(:conditions => {:project_id => self.project, :name => Setting.forum_name}) motion_topic = Message.create! :board_id => main_board.id, :subject => self.title, :content => self.description, :author_id => self.author_id self.update_attribute(:topic_id,motion_topic.id) end def visibility_level_description Role.first(:conditions => {:position => self.visibility_level}).name end def binding_level_description Role.first(:conditions => {:position => self.binding_level}).name end def execute_action return if self.state != STATE_PASSED case self.variation when VARIATION_GENERAL when VARIATION_EXTRAORDINARY when VARIATION_NEW_MEMBER return if !self.concerned_user.contributor_of?(self.project) self.concerned_user.add_as_member(self.project) when VARIATION_NEW_CORE return if !self.concerned_user.member_of?(self.project) self.concerned_user.add_as_core(self.project) when VARIATION_FIRE_MEMBER return if !self.concerned_user.member_of?(self.project) self.concerned_user.add_as_contributor(self.project) when VARIATION_FIRE_CORE return if !self.concerned_user.core_member_of?(self.project) self.concerned_user.add_as_member(self.project) when VARIATION_BOARD_PUBLIC when VARIATION_BOARD_PRIVATE when VARIATION_HOURLY_TYPE end end def send_announce self.send_later :announce end def announce admin = User.sysadmin self.project.all_members.each do |member| user = member.user Notification.create :recipient_id => user.id, :variation => 'motion_started', :params => {:motion_title => self.title, :motion_description => self.description, :enterprise_id => self.project.root.id}, :sender_id => self.author_id, :source_id => self.id, :source_type => "Motion", :expiration => self.ends_on if user.allowed_to_see_motion?(self) unless self.concerned_user_id == user.id end end def announce_passed admin = User.sysadmin LogActivityStreams.write_single_activity_stream(User.sysadmin,:name,self,:title,:passed_a,:motions, 0, nil,{}) News.create :project_id => self.project.id, :title => "Passed! #{self.title}", :summary => "#{self.title} has passed", :description => "#{self.description}", :author_id => admin end end ================================================ FILE: app/models/motion_vote.rb ================================================ class MotionVote < ActiveRecord::Base belongs_to :user belongs_to :motion before_create :remove_similar_votes before_save :set_binding after_save :update_agree_total, :remove_notifications named_scope :belong_to_user, lambda {|user_id| {:conditions => {:user_id => user_id}} } do def default find(:first, :conditions => { :is_default => true }) end end named_scope :history, :order => 'updated_at DESC', :include => :user def project self.motion.project end def remove_similar_votes MotionVote.delete_all(:motion_id => motion_id, :user_id => user_id) end def set_binding self.isbinding = self.user.binding_voter_of_motion?(self.motion) return true end def action_description if self.motion.motion_type == Motion::TYPE_SHARE if (self.points < 0) l("label_motion_vote_action_share_disagree",:points => self.points * -1 ) elsif (self.points == 0) l("label_motion_vote_action_share_neutral",:points => self.points) else l("label_motion_vote_action_share_agree",:points => self.points) end else l("label_motion_vote_action#{self.points + 10000}") end end def update_agree_total @motion = self.motion if self.isbinding @motion.agree = MotionVote.sum(:points, :conditions => "motion_id = '#{@motion.id}' AND points > 0 AND isbinding='true'") @motion.disagree = MotionVote.sum(:points, :conditions => "motion_id = '#{@motion.id}' AND points < 0 AND isbinding='true'") * -1 @motion.agree_total = @motion.agree - @motion.disagree else @motion.agree_nonbind = MotionVote.sum(:points, :conditions => "motion_id = '#{@motion.id}' AND points > 0 AND isbinding='false'") @motion.disagree_nonbind = MotionVote.sum(:points, :conditions => "motion_id = '#{@motion.id}' AND points < 0 AND isbinding='false'") * -1 @motion.agree_total_nonbind = @motion.agree_nonbind - @motion.disagree_nonbind end @motion.save end def remove_notifications Notification.update_all "state = #{Notification::STATE_ARCHIVED}" , ["variation = 'motion_started' AND source_id = ? AND recipient_id = ?", self.motion_id, self.user_id] end end ================================================ FILE: app/models/news.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # class News < ActiveRecord::Base belongs_to :project belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_at" validates_presence_of :title, :description validates_length_of :title, :maximum => 60 validates_length_of :summary, :maximum => 255 after_save :send_mentions acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_news, project) end # Returns the mail adresses of users that should be notified def recipients notified = project.notified_users notified.reject! {|user| !visible?(user) || user.pref[:no_emails]} notified.collect(&:mail) end # returns latest news for projects visible by user def self.latest(user = User.current, count = 5) find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news) + " (created_at > '#{Time.now.advance :days => (Setting::DAYS_FOR_LATEST_NEWS * -1)}')", :include => [ :author, :project ], :order => "#{News.table_name}.created_at DESC") end def send_mentions Mention.parse(self, self.author_id) end def mention(mentioner_id, mentioned_id, mention_text) Notification.create :recipient_id => mentioned_id, :variation => 'mention', :params => {:mention_text => self.description, :url => {:controller => "news", :action => "show", :id => self.id}, :title => self.title}, :sender_id => mentioner_id, :source_id => self.id, :source_type => "News" end end ================================================ FILE: app/models/news_observer.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class NewsObserver < ActiveRecord::Observer def after_create(news) Mailer.send_later(:deliver_news_added,news) if Setting.notified_events.include?('news_added') end end ================================================ FILE: app/models/notification.rb ================================================ class Notification < ActiveRecord::Base serialize :params belongs_to :recipient, :class_name => 'User', :foreign_key => 'recipient_id' belongs_to :sender, :class_name => 'User', :foreign_key => 'sender_id' # Returns all active, non responded, non-expired notifications named_scope :allactive, :conditions => ["state = 0 AND (expiration is null or expiration >=?)", Time.new.to_date] before_create :remove_mention_duplicates STATE_DEACTIVATED = -1 STATE_ACTIVE = 0 STATE_RESPONDED = 1 STATE_ARCHIVED = 2 STATE_RECINDED = 3 def mark_as_responded self.state = STATE_RESPONDED self.params = nil self.save end #returns true if notification is of a mention type def mention? self.variation == "mention" end # Returns true or false based on if this user has any notifications that haven't been responded to def self.unresponded? self.unresponded_count > 0 ? true : false end # Returns the number of unresponded notifications for this user def self.unresponded_count self.count(:conditions => ["recipient_id=? AND (expiration is null or expiration >=?) AND state = 0", User.current, Time.new.to_date]) end def self.unresponded self.find(:all, :conditions => ["recipient_id=? AND (expiration is null or expiration >=?) AND state = 0", User.current, Time.new.to_date]) end def remove_mention_duplicates return unless mention? Notification.update_all(["state = ?", STATE_ARCHIVED], {:variation => 'mention', :recipient_id => self.recipient_id, :source_id => self.source_id, :source_type => self.source_type}) end # Deactivates all unanswered notifications for a particular variation and source id def self.recind(variation, source_id, sender_id) @notifications = self.find(:all, :conditions => ["source_id =? AND variation =? AND state =0 AND params like'%:sender_id => ?%'", source_id, variation, sender_id]) @notifications.each do |@n| @n.state = STATE_RECINDED @n.save end end # Deactivates all unanswered notifications for a particular variation and source id def self.deactivate_all(variation, source_id) update_all(variation,source_id,0,STATE_DEACTIVATED) end def self.activate_all(variation, source_id) update_all(variation,source_id,-1,STATE_ACTIVE) end end ================================================ FILE: app/models/open_id_authentication_association.rb ================================================ class OpenIdAuthenticationAssociation < ActiveRecord::Base end ================================================ FILE: app/models/open_id_authentication_nonces.rb ================================================ class OpenIdAuthenticationNonces < ActiveRecord::Base end ================================================ FILE: app/models/personal_welcome.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class PersonalWelcome < ActiveRecord::Base belongs_to :user def self.deliver projects = Project.find(:all, :conditions => "created_at > '#{Time.now.advance :days => -7}' AND parent_id is null AND owner_id not in (select user_id from personal_welcomes)").group_by{|p| p.owner_id} projects.each_pair do |owner_id,project_list| project_list.sort! {|x,y| x.created_at <=> y.created_at } Mailer.send_later(:deliver_personal_welcome,User.find(owner_id),Project.find(project_list[0].id)) PersonalWelcome.create :user_id => owner_id end end end ================================================ FILE: app/models/plan.rb ================================================ class Plan < ActiveRecord::Base FREE_CODE = 0 def free? return self.code == FREE_CODE end def self.free find_by_code(FREE_CODE) end end ================================================ FILE: app/models/plugin_schema_info.rb ================================================ class PluginSchemaInfo < ActiveRecord::Base set_table_name 'plugin_schema_info' end ================================================ FILE: app/models/project.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Project < ActiveRecord::Base # Project statuses STATUS_ACTIVE = 1 STATUS_LOCKED = 2 #private workstream, and user is overdue STATUS_ARCHIVED = 9 belongs_to :enterprise belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id' # Specific overidden Activities has_many :all_members,:class_name => 'Member', :include => [:user, :roles], :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}", :order => "firstname ASC" has_many :administrators, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_ADMINISTRATOR}", :order => "firstname ASC" has_many :core_members, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_CORE_MEMBER}", :order => "firstname ASC" has_many :members, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_MEMBER}", :order => "firstname ASC", :dependent => :destroy has_many :board_members, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_BOARD}", :order => "firstname ASC" has_many :contributors, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_CONTRIBUTOR}", :order => "firstname ASC" has_many :binding_members, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_MEMBER} OR #{Role.table_name}.builtin=#{Role::BUILTIN_CORE_MEMBER} OR #{Role.table_name}.builtin=#{Role::BUILTIN_BOARD} OR #{Role.table_name}.builtin=#{Role::BUILTIN_ADMINISTRATOR}", :order => "firstname ASC" has_many :enterprise_members, :class_name => 'Member', :include => [:user,:roles], :conditions => "#{Role.table_name}.builtin=#{Role::BUILTIN_CONTRIBUTOR} OR #{Role.table_name}.builtin=#{Role::BUILTIN_MEMBER} OR #{Role.table_name}.builtin=#{Role::BUILTIN_CORE_MEMBER} OR #{Role.table_name}.builtin=#{Role::BUILTIN_BOARD} OR #{Role.table_name}.builtin=#{Role::BUILTIN_ADMINISTRATOR}", :order => "firstname ASC" has_many :member_users, :class_name => 'Member', :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}", :order => "firstname ASC" has_many :users, :through => :all_members has_many :credit_distributions, :dependent => :delete_all has_many :enabled_modules, :dependent => :delete_all has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_at DESC", :include => [:status, :tracker] has_many :issue_votes, :through => :issues has_many :issue_changes, :through => :issues, :source => :journals has_many :queries, :dependent => :delete_all has_many :documents, :dependent => :destroy has_many :news, :dependent => :delete_all, :include => :author has_many :boards, :dependent => :destroy, :order => "position ASC" has_many :messages, :through => :boards has_one :wiki, :dependent => :destroy has_many :shares, :dependent => :delete_all has_many :credits, :dependent => :delete_all, :order => 'created_at ASC' has_many :retros, :dependent => :delete_all has_many :reputations, :dependent => :delete_all has_many :motions, :dependent => :delete_all has_many :hourly_types, :dependent => :delete_all has_many :activity_streams, :dependent => :delete_all #TODO: include sub workstreams here! has_many :invitations, :dependent => :delete_all acts_as_nested_set :order => 'name', :dependent => :destroy acts_as_attachable :view_permission => :view_files, :delete_permission => :manage_files acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"}, :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}, :author => nil acts_as_fleximage do begin require_image false if RAILS_ENV == 'production' s3_bucket 'bettermeans_workstream_logos' else image_directory '/public/help' end preprocess_image do |image| image.resize '200x600' end rescue end end attr_protected :status, :enabled_module_names validates_presence_of :name, :identifier validates_uniqueness_of :identifier validates_associated :wiki validates_length_of :name, :maximum => 50 validates_length_of :homepage, :maximum => 255 validates_length_of :identifier, :in => 1..20 # reserved words validates_exclusion_of :identifier, :in => %w( new ) before_destroy :delete_all_members named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} named_scope :all_public, { :conditions => { :is_public => true } } named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } } named_scope :all_roots, {:conditions => "parent_id is null"} named_scope :all_children, {:conditions => "parent_id is not null"} named_scope :does_not_belong_to, lambda {|id| {:conditions => ["owner_id <> :s", {:s => id}], :order => 'name' } } reportable :daily_new_projects, :aggregation => :count, :limit => 14 reportable :weekly_new_projects, :aggregation => :count, :grouping => :week, :limit => 20 def project_id self.id end def all_tags(term = '') ActsAsTaggableOn::Tag.find_by_sql(["select name from tags inner join taggings on taggings.tag_id = tags.id where taggings.project_id = ? and name like '%#{term}%'",self.id]).map{|t| t.name}.uniq.sort end def graph_data valid_kids = children.select{|c| c.active?} if valid_kids.size > 0 mychildren = valid_kids.collect{ |node| node.graph_data } else mychildren = [] end diameter = issues.length**0.5/3.142*6 { :id => self.identifier, :name => name, :children => mychildren, :data => {:$dim => diameter,:$angularWidth => diameter, :$color => '#fdd13d' } } end #returns array of project ids that are children of this project. includes id of current project def sub_project_array array = [self.id] self.children.each do |child| array += child.sub_project_array end array end #returns array of project ids that are children of this project. includes id of current project that are visible to user def sub_project_array_visible_to(user) if self.visible_to(user) array = [self.id] else array = [] end self.children.each do |child| array += child.sub_project_array_visible_to(user) end array end def graph_data2 valid_kids = children.select{|c| c.active?} if valid_kids.size > 0 mychildren = valid_kids.collect{ |node| node.graph_data2 } else mychildren = [] end diameter = issues.length**0.5/3.142*3 { :levelDistance => issues.length,:id => self.identifier, :name => name, :children => mychildren, :data => {:$dim => diameter,:$angularWidth => diameter, :$color => '#fdd13d' } } end def identifier=(identifier) super unless identifier_frozen? end def identifier_frozen? errors[:identifier].nil? && !(new_record? || identifier.blank?) end def self.latest_public(count=10, offset=0) filter = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND " + "#{Project.table_name}.is_public = #{connection.quoted_true}" all_roots.find( :all, :limit => count, :conditions => filter, :order => "created_at DESC", :offset => offset ) end def self.most_active_public(count=10, offset=0) filter = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND " + "#{Project.table_name}.is_public = #{connection.quoted_true}" all_roots.find( :all, :limit => count, :conditions => filter, :order => "activity_total DESC", :offset => offset ) end # returns most active projects # non public projects will be returned only if user is a member of those def self.most_active(user=nil, count=10, root=false, offset=0) if root all_roots.find(:all, :limit => count, :conditions => visible_by(user), :order => "activity_total DESC", :offset => offset) else all_children.find(:all, :limit => count, :conditions => visible_by(user), :order => "activity_total DESC", :offset => offset) end end #Returns true if project is visible by user def visible_to(user) return true if user.admin? return false unless active? return true if is_public return true if user.allowed_to_see_project?(self) return false end # Returns a SQL :conditions string used to find all active projects for the specified user. # # Examples: # Projects.visible_by(admin) => "projects.status = 1" # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1" def self.visible_by(user=nil) user ||= User.anonymous if user && user.admin? return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" elsif user && user.memberships.any? return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))" else return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}" end end def fetch_credits(with_subprojects) with_subprojects ||= 'true' if with_subprojects == 'true' conditions = {} conditions[:project_id] = self.sub_project_array Credit.all(:conditions => conditions, :order => 'created_at ASC') else self.credits end end def self.allowed_to_condition(user, permission, options={}) statements = [] base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" if perm = Redmine::AccessControl.permission(permission) unless perm.project_module.nil? # If the permission belongs to a project module, make sure the module is enabled base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" end end if options[:project] project_statement = "#{Project.table_name}.id = #{options[:project].id}" project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] base_statement = "(#{project_statement}) AND (#{base_statement})" end if user.admin? # no restriction else statements << "1=0" if user.logged? if Role.non_member.allowed_to?(permission) && !options[:member] statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" end allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id} statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any? else if Role.anonymous.allowed_to?(permission) && !options[:member] # anonymous user allowed on public project statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" end end end statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))" end # Returns the Systemwide and project specific activities def activities(include_inactive=false) if include_inactive return all_activities else return active_activities end end # Returns a :conditions SQL string that can be used to find the issues associated with this project. # # Examples: # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" # project.project_condition(false) => "projects.id = 1" def project_condition(with_subprojects) cond = "#{Project.table_name}.id = #{id}" cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects cond end def self.find(*args) if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/) project = find_by_identifier(*args) raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil? project else super end end def active? self.status == STATUS_ACTIVE end def archived? self.status == STATUS_ARCHIVED end def locked? self.status == STATUS_LOCKED end def lock self.update_attribute(:status, STATUS_LOCKED) if active? && !locked? end def unlock self.update_attribute(:status, STATUS_ACTIVE) if locked? end def enterprise? self.parent_id.nil? end # Archives the project and its descendants def archive Project.transaction do archive! end true end # Unarchives the project # All its ancestors must be active def unarchive return false if ancestors.detect {|a| !a.active?} update_attribute :status, STATUS_ACTIVE end # Returns an array of projects the project can be moved to # by the current user def allowed_parents return @allowed_parents if @allowed_parents @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) @allowed_parents = @allowed_parents - self_and_descendants if User.current.allowed_to?(:add_project, nil, :global => true) @allowed_parents << nil end unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent) @allowed_parents << parent end @allowed_parents end # Sets the parent of the project with authorization check def set_allowed_parent!(p) unless p.nil? || p.is_a?(Project) if p.to_s.blank? p = nil else p = Project.find_by_id(p) return false unless p end end if p.nil? if !new_record? && allowed_parents.empty? return false end elsif !allowed_parents.include?(p) return false end set_parent!(p) end # Sets the parent of the project # Argument can be either a Project, a String, a Fixnum or nil # WARNING: This doesn't move the children for the project, if moving a project use: move_to_child_of def set_parent!(p) unless p.nil? || p.is_a?(Project) if p.to_s.blank? p = nil else p = Project.find_by_id(p) return false unless p end end if p == parent && !p.nil? # Nothing to do true elsif p.nil? || (p.active? && move_possible?(p)) # Insert the project so that target's children or root projects stay alphabetically sorted sibs = (p.nil? ? self.class.roots : p.children) to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase } if to_be_inserted_before move_to_left_of(to_be_inserted_before) elsif p.nil? if sibs.empty? # move_to_root adds the project in first (ie. left) position move_to_root else move_to_right_of(sibs.last) unless self == sibs.last end else # move_to_child_of adds the project in last (ie.right) position move_to_child_of(p) end true else # Can not move to the given target false end end # Returns an array of the trackers used by the project and its active sub projects def rolled_up_trackers @rolled_up_trackers ||= Tracker.find(:all, :include => :projects, :select => "DISTINCT #{Tracker.table_name}.*", :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], :order => "#{Tracker.table_name}.position") end # Returns a hash of project users grouped by role def users_by_role all_members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| m.roles.each do |r| h[r] ||= [] h[r] << m.user end h end end # Returns a hash of active project users def active_members all_members.find(:all, :conditions => "roles.builtin = #{Role::BUILTIN_ACTIVE}",:include => [:user, :roles], :order => "firstname ASC") end # Returns a hash of project users with clearance def clearance_members all_members.find(:all, :conditions => "roles.builtin = #{Role::BUILTIN_CLEARANCE}",:include => [:user, :roles], :order => "firstname ASC") end # Returns a hash of contributers def contributor_list self.contributors end # Returns a hash of active project users grouped by role def member_list self.members end # Returns a hash of active project users grouped by role def core_member_list self.core_members end # Number of members who are active and have a binding vote def active_binding_members_count active_members = self.active_members.collect{|member| member.user_id} active_binding_members_count = (self.root.core_member_list.collect{|member| member.user_id} & active_members).length active_binding_members_count += (self.root.member_list.collect{|member| member.user_id} & active_members).length end def binding_members_count self.root.core_member_list.count + self.root.member_list.count + self.root.member_list.count end # returns count of all users for this role and higher roles def role_and_above_count(position) all_members.count(:all, :conditions => "roles.position <= #{position}", :group => "user_id").length end # Retrieves a list of all active users for the past (x days) and refreshes their roles # Also refreshes members with clearance def refresh_active_members return unless self.active? u = {} #Adding from activity stream self.activity_streams.recent.each do |as| u[as.actor_id] ||= as.actor_id end #Adding voters (do we really need this?) issues.each do |issue| next if (issue.updated_at.advance :days => Setting::DAYS_FOR_ACTIVE_MEMBERSHIP) > Time.now issue.issue_votes.each do |iv| u[iv.user_id] ||= iv.user_id if (iv.updated_at.advance :days => Setting::DAYS_FOR_ACTIVE_MEMBERSHIP) > Time.now end end u.delete nil u.delete User.sysadmin.id #removing active members that aren't in new list self.active_members.each do |m| if u[m.user_id].nil? new_m = Member.find(m.id) #for some amazing reason, I have to reload the member to get all its roles! Otherwise, I only get the active roles a = new_m.role_ids a.delete Role.active.id new_m.role_ids = a end end #adding active members that are in new list that aren't already active existing_active_members = self.active_members.collect(&:user_id) u.keys.each do |user_id| begin user = User.find(user_id) user.add_to_project(self, Role.active) unless existing_active_members.include? user_id rescue #user not found (when deleting users) end end unless self.is_public? #giving clearance to all active members self.active_members.each do |m| User.find(m.user_id).add_to_project(self, Role.clearance) end #giving all root binding members clearance self.root.binding_members.each do |m| User.find(m.user_id).add_to_project(self, Role.clearance) end end end # Deletes all project's members def delete_all_members me, mr = Member.table_name, MemberRole.table_name connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") Member.delete_all(['project_id = ?', id]) end # Users issues can be assigned to def assignable_users all_members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort end # Returns the mail adresses of users that should be always notified on project events def recipients all_members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail} end # Returns the users that should be notified on project events def notified_users all_members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user} end def project self end def <=>(project) name.downcase <=> project.name.downcase end def to_s name end def name_with_ancestors b = [] ancestors = (project.root? ? [] : project.ancestors.visible) if ancestors.any? root = ancestors.shift b << root.name if ancestors.size > 2 b << '...' ancestors = ancestors[-2, 2] end b += ancestors.collect {|p| p.name } end b << project.name b.join( ' » ') end # Returns a short description of the projects (first lines) def short_description(length = 255) description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description end # Return true if this project is allowed to do the specified action. # action can be: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a permission Symbol (eg. :edit_project) def allows_to?(action) if action.is_a? Hash allowed_actions.include? "#{action[:controller]}/#{action[:action]}" else allowed_permissions.include? action end end def module_enabled?(module_name) module_name = module_name.to_s enabled_modules.detect {|m| m.name == module_name} end def credits_enabled? !module_enabled?(:credits).nil? end def enabled_module_names=(module_names) if module_names && module_names.is_a?(Array) module_names = module_names.collect(&:to_s) # remove disabled modules enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)} # add new modules module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)} else enabled_modules.clear end end # Returns an auto-generated project identifier based on the last identifier used def self.next_identifier p = Project.find(:first, :order => 'created_at DESC') return 'A' if p.nil? next_id = p.identifier.to_s.succ while Project.exists?(:identifier => next_id) next_id = next_id.succ end next_id end # Copies and saves the Project instance based on the +project+. # Duplicates the source project's: # * Wiki # * Categories # * Issues # * Members # * Queries # # Accepts an +options+ argument to specify what to copy # # Examples: # project.copy(1) # => copies everything # project.copy(1, :only => 'members') # => copies members only # project.copy(1, :only => ['members', 'versions']) # => copies members and versions def copy(project, options={}) project = project.is_a?(Project) ? project : Project.find(project) to_be_copied = %w(wiki issue_categories issues members queries boards) to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil? Project.transaction do if save reload to_be_copied.each do |name| send "copy_#{name}", project end save end end end # Copies +project+ and returns the new instance. This will not save # the copy def self.copy_from(project) begin project = project.is_a?(Project) ? project : Project.find(project) if project # clear unique attributes attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') copy = Project.new(attributes) copy.enabled_modules = project.enabled_modules copy.trackers = project.trackers return copy else return nil end rescue ActiveRecord::RecordNotFound return nil end end def team_points_for(user, options={}) user.team_points_for(project) end #highest priority for open items in this project def highest_pri self.issues.maximum(:pri, :conditions => {:status_id => IssueStatus.open.id }) || -9999 end def before_validation_on_create self.enterprise_id = self.parent.enterprise_id unless self.parent.nil? self.identifier = Project.next_identifier self.invitation_token = Token.generate_token_value if self.credits_enabled? self.trackers = Tracker.all else self.trackers = Tracker.no_credits end return true end #Setup default forum for workstream def after_create logger.info { "entering after create" } #Send notification of request or invitation to recipient Board.create! :project_id => id, :name => Setting.forum_name, :description => Setting.forum_description + name self.refresh_activity_line self.save! return true end def set_owner if !self.root? self.update_attribute(:owner_id,self.root.owner_id) elsif owner_id.nil? self.owner_id = User.current.id if self.parent_id.nil? admins = self.administrators.sort {|x,y| x.created_at <=> y.created_at} if admins.length > 0 self.owner_id = admins[0].user_id self.save else core = self.core_members.sort {|x,y| x.created_at <=> y.created_at} self.owner_id = core[0].user_id if core.length > 0 self.save end end end #Returns true if threshold of points that haven't been included in a retrospective have been created def ready_for_retro? return false if !credits_enabled? total_unretroed = Issue.sum(:points, :conditions => {:status_id => IssueStatus.accepted.id,:retro_id => Retro::NOT_STARTED_ID, :project_id => id}) return true if total_unretroed >= Setting::RETRO_CREDIT_THRESHOLD #Getting most recent issue that's not part of retrospective first_issue = Issue.first(:conditions => {:project_id => self.id, :status_id => IssueStatus.accepted, :retro_id => Retro::NOT_STARTED_ID}, :order => "updated_at asc") return false if first_issue == nil return true if (first_issue.updated_at.advance :days => Setting::RETRO_DAY_THRESHOLD) < Time.now return false end #Starts a new retrospective for this project def start_new_retro return false if !credits_enabled? from_date = issues.first(:conditions => {:retro_id => Retro::NOT_STARTED_ID}, :order => "updated_at ASC").updated_at total_points = issues.sum(:points, :conditions => {:retro_id => Retro::NOT_STARTED_ID}) @retro = Retro.create :project_id => id, :status_id => Retro::STATUS_INPROGRESS, :to_date => DateTime.now, :from_date => from_date, :total_points => total_points Issue.update_all("retro_id = #{@retro.id}" , "project_id = #{id} AND retro_id = #{Retro::NOT_STARTED_ID}") @retro.announce_start end #Starts a new retrospective if it's ready def start_retro_if_ready start_new_retro if ready_for_retro? end def refresh_activity_line date_array = Hash.new(0) for i in (1..Setting::ACTIVITY_LINE_LENGTH) date_array[(Date.today - i).to_s] = 0 end #All issue votes iv_array = issue_votes.count(:group => 'DATE(issue_votes.created_at)', :conditions => "issue_votes.created_at > '#{(Date.today - Setting::ACTIVITY_LINE_LENGTH).to_s}'") my_line = date_array.merge iv_array #all issues iv_array = issues.count(:group => 'DATE(issues.updated_at)', :conditions => "issues.updated_at > '#{(Date.today - Setting::ACTIVITY_LINE_LENGTH).to_s}'") my_line + iv_array #all board messages iv_array = messages.count(:group => 'DATE(messages.updated_at)', :conditions => "messages.updated_at > '#{(Date.today - Setting::ACTIVITY_LINE_LENGTH).to_s}'") my_line + iv_array #all journals iv_array = issue_changes.count(:group => 'DATE(journals.updated_at)', :conditions => "journals.updated_at > '#{(Date.today - Setting::ACTIVITY_LINE_LENGTH).to_s}'") my_line + iv_array self.children.each do |sub_project| my_line + sub_project.refresh_activity_line end self.activity_line = (my_line.sort.collect {|v| v[1]}).inspect.delete("[").delete("]") weight = 1 activity_total = 0 my_line.sort.each do |v| logger.info { "activity total #{activity_total} weight #{weight} value #{v[1]}" } activity_total = activity_total + (weight**1.7 * v[1]) weight = weight + 1 end self.activity_total = activity_total self.save my_line end def activity_line_max self.activity_line.split(',').max{|a,b| a.to_f <=> b.to_f} end def activity_line_show(length) activity_line.split(",").slice(self.activity_line.split(",").length - length,length).join(",") end def volunteer? return self.volunteer == true end def calculate_storage sum = 0 documents.each do |d| sum += d.size end issues.each do |d| sum += d.size end self.storage = sum self.save end def allowed_actions @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten end def refresh_issue_count self.update_attribute(:issue_count,Issue.count(:conditions => ["project_id = ? AND (retro_id < 0 OR retro_id is null)", self.id]) ) self.parent.refresh_issue_count_sub unless self.root? end def refresh_issue_count_sub count_sub = self.children.inject(0){|sum,p| sum + p.issue_count + p.issue_count_sub} self.update_attribute(:issue_count_sub, count_sub) self.parent.refresh_issue_count_sub unless self.root? end def update_last_item self.update_attribute(:last_item_updated_on, DateTime.now) self.parent.update_last_item_sub unless self.root? end def update_last_item_sub self.update_attribute(:last_item_sub_updated_on, DateTime.now) self.parent.update_last_item_sub unless self.root? end private # Copies wiki from +project+ def copy_wiki(project) # Check that the source project has a wiki first unless project.wiki.nil? self.wiki ||= Wiki.new wiki.attributes = project.wiki.attributes.dup.except("id", "project_id") project.wiki.pages.each do |page| new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_at")) new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_at", "parent_id")) new_wiki_page.content = new_wiki_content wiki.pages << new_wiki_page end end end # Copies issues from +project+ def copy_issues(project) # Stores the source issue id as a key and the copied issues as the # value. Used to map the two togeather for issue relations. issues_map = {} project.issues.each do |issue| new_issue = Issue.new new_issue.copy_from(issue) self.issues << new_issue issues_map[issue.id] = new_issue end # Relations after in case issues related each other project.issues.each do |issue| new_issue = issues_map[issue.id] # Relations issue.relations_from.each do |source_relation| new_issue_relation = IssueRelation.new new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") new_issue_relation.issue_to = issues_map[source_relation.issue_to_id] if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations? new_issue_relation.issue_to = source_relation.issue_to end new_issue.relations_from << new_issue_relation end issue.relations_to.each do |source_relation| new_issue_relation = IssueRelation.new new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") new_issue_relation.issue_from = issues_map[source_relation.issue_from_id] if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations? new_issue_relation.issue_from = source_relation.issue_from end new_issue.relations_to << new_issue_relation end end end # Copies members from +project+ def copy_members(project) project.all_members.each do |member| new_member = Member.new new_member.attributes = member.attributes.dup.except("id", "project_id", "created_at") new_member.role_ids = member.role_ids.dup new_member.project = self self.all_members << new_member end end # Copies queries from +project+ def copy_queries(project) project.queries.each do |query| new_query = Query.new new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") new_query.sort_criteria = query.sort_criteria if query.sort_criteria new_query.project = self self.queries << new_query end end # Copies boards from +project+ def copy_boards(project) project.boards.each do |board| new_board = Board.new new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id") new_board.project = self self.boards << new_board end end def allowed_permissions @allowed_permissions ||= begin module_names = enabled_modules.collect {|m| m.name} Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name} end end # Archives subprojects recursively def archive! children.each do |subproject| subproject.send :archive! end update_attribute :status, STATUS_ARCHIVED end end ================================================ FILE: app/models/query.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # class QueryColumn attr_accessor :name, :sortable, :groupable, :default_order include Redmine::I18n def initialize(name, options={}) self.name = name self.sortable = options[:sortable] self.groupable = options[:groupable] || false if groupable == true self.groupable = name.to_s end self.default_order = options[:default_order] end def caption l("field_#{name}") end # Returns true if the column is sortable, otherwise false def sortable? !sortable.nil? end def value(issue) issue.send name end end class Query < ActiveRecord::Base class StatementInvalid < ::ActiveRecord::StatementInvalid end belongs_to :project belongs_to :user serialize :filters serialize :column_names serialize :sort_criteria, Array attr_protected :project_id, :user_id validates_presence_of :name, :on => :save validates_length_of :name, :maximum => 255 @@operators = { "=" => :label_equals, "!" => :label_not_equals, "o" => :label_open_issues, "c" => :label_closed_issues, "!*" => :label_none, "*" => :label_all, ">=" => :label_greater_or_equal, "<=" => :label_less_or_equal, " :label_in_less_than, ">t+" => :label_in_more_than, "t+" => :label_in, "t" => :label_today, "w" => :label_this_week, ">t-" => :label_less_than_ago, " :label_more_than_ago, "t-" => :label_ago, "~" => :label_contains, "!~" => :label_not_contains } cattr_reader :operators @@operators_by_filter_type = { :list => [ "=", "!" ], :list_status => [ "o", "=", "!", "c", "*" ], :list_optional => [ "=", "!", "!*", "*" ], :list_subprojects => [ "*", "!*", "=" ], :date => [ "t+", "t+", "t", "w", ">t-", " [ ">t-", " [ "=", "~", "!", "!~" ], :text => [ "~", "!~" ], :integer => [ "=", ">=", "<=", "!*", "*" ] } cattr_reader :operators_by_filter_type @@available_columns = [ QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), QueryColumn.new(:pri, :sortable => "#{Issue.table_name}.pri", :default_order => 'desc', :groupable => true), QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), QueryColumn.new(:author), QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true), QueryColumn.new(:updated_at, :sortable => "#{Issue.table_name}.updated_at", :default_order => 'desc'), QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), QueryColumn.new(:created_at, :sortable => "#{Issue.table_name}.created_at", :default_order => 'desc'), ] cattr_reader :available_columns # BUGBUG: this initialize won't work consistently: # http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html # better to make this an after_initialize def initialize(attributes = nil) super attributes self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } end def after_initialize # Store the fact that project is nil (used in #editable_by?) @is_for_all = project.nil? end def validate filters.each_key do |field| errors.add label_for(field), :blank unless # filter requires one or more values (values_for(field) and !values_for(field).first.blank?) or # filter doesn't require any value ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) end if filters end def editable_by?(user) return false unless user # Admin can edit them all and regular users can edit their private queries return true if user.admin? || (!is_public && self.user_id == user.id) # Members can not edit public queries that are for all project (only admin is allowed to) is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) end def available_filters return @available_filters if @available_filters trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, "subject" => { :type => :text, :order => 8 }, "created_at" => { :type => :date_past, :order => 9 }, "updated_at" => { :type => :date_past, :order => 10 }, "start_date" => { :type => :date, :order => 11 }, "due_date" => { :type => :date, :order => 12 }, "estimated_hours" => { :type => :integer, :order => 13 } } user_values = [] user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? if project user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } else # members of the user's projects # OPTIMIZE: Is selecting from users per project (N+1) user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] } end @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty? @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty? if User.current.logged? @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] } end if project unless @project.descendants.active.empty? @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } } end else # global filters for cross project issue list end @available_filters end def add_filter(field, operator, values) # values must be an array return unless values and values.is_a? Array # check if field is defined as an available filter if available_filters.has_key? field filter_options = available_filters[field] filters[field] = {:operator => operator, :values => values } end end def add_short_filter(field, expression) return unless expression parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first add_filter field, (parms[0] || "="), [parms[1] || ""] end def has_filter?(field) filters and filters[field] end def operator_for(field) has_filter?(field) ? filters[field][:operator] : nil end def values_for(field) has_filter?(field) ? filters[field][:values] : nil end def label_for(field) label = available_filters[field][:name] if available_filters.has_key?(field) label ||= field.gsub(/\_id$/, "") end def available_columns return @available_columns if @available_columns @available_columns = Query.available_columns end # Returns an array of columns that can be used to group the results def groupable_columns available_columns.select {|c| c.groupable} end def columns if has_default_columns? available_columns.select do |c| # Adds the project column by default for cross-project lists Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?) end else # preserve the column_names order column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact end end def column_names=(names) if names names = names.select {|n| n.is_a?(Symbol) || !n.blank? } names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } # Set column_names to nil if default columns if names.map(&:to_s) == Setting.issue_list_default_columns names = nil end end write_attribute(:column_names, names) end def has_column?(column) column_names && column_names.include?(column.name) end def has_default_columns? column_names.nil? || column_names.empty? end def sort_criteria=(arg) c = [] if arg.is_a?(Hash) arg = arg.keys.sort.collect {|k| arg[k]} end c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} write_attribute(:sort_criteria, c) end def sort_criteria read_attribute(:sort_criteria) || [] end def sort_criteria_key(arg) sort_criteria && sort_criteria[arg] && sort_criteria[arg].first end def sort_criteria_order(arg) sort_criteria && sort_criteria[arg] && sort_criteria[arg].last end # Returns the SQL sort order that should be prepended for grouping def group_by_sort_order if grouped? && (column = group_by_column) column.sortable.is_a?(Array) ? column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : "#{column.sortable} #{column.default_order}" end end # Returns true if the query is a grouped query def grouped? !group_by.blank? end def group_by_column groupable_columns.detect {|c| c.name.to_s == group_by} end def group_by_statement group_by_column.groupable end def project_statement project_clauses = [] if project && !@project.descendants.active.empty? ids = [project.id] if has_filter?("subproject_id") case operator_for("subproject_id") when '=' # include the selected subprojects ids += values_for("subproject_id").each(&:to_i) when '!*' # main project only else # all subprojects ids += project.descendants.collect(&:id) end elsif Setting.display_subprojects_issues? ids += project.descendants.collect(&:id) end project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') elsif project project_clauses << "#{Project.table_name}.id = %d" % project.id end project_clauses << Project.allowed_to_condition(User.current, :view_issues) project_clauses.join(' AND ') end def statement # filters clauses filters_clauses = [] filters.each_key do |field| next if field == "subproject_id" v = values_for(field).clone next unless v and !v.empty? operator = operator_for(field) # "me" value subsitution if %w(assigned_to_id author_id watcher_id).include?(field) v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") end sql = '' if field == 'watcher_id' db_table = Watcher.table_name db_field = 'user_id' sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " sql << sql_for_field(field, '=', v, db_table, db_field) + ')' else # regular field db_table = Issue.table_name db_field = field sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')' end filters_clauses << sql end if filters and valid? (filters_clauses << project_statement).join(' AND ') end # Returns the issue count def issue_count Issue.count(:include => [:status, :project], :conditions => statement) rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end # Returns the issue count by group or nil if query is not grouped def issue_count_by_group r = nil if grouped? begin # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) rescue ActiveRecord::RecordNotFound r = {nil => issue_count} end c = group_by_column end r rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end # Returns the issues # Valid options are :order, :offset, :limit, :include, :conditions def issues(options={}) order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') order_option = nil if order_option.blank? Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, :conditions => Query.merge_conditions(statement, options[:conditions]), :order => order_option, :limit => options[:limit], :offset => options[:offset] rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end # Returns the journals # Valid options are :order, :offset, :limit def journals(options={}) Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], :conditions => statement, :order => options[:order], :limit => options[:limit], :offset => options[:offset] rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end private # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ def sql_for_field(field, operator, value, db_table, db_field) sql = '' case operator when "=" sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" when "!" sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" when "!*" sql = "#{db_table}.#{db_field} IS NULL" when "*" sql = "#{db_table}.#{db_field} IS NOT NULL" when ">=" sql = "#{db_table}.#{db_field} >= #{value.first.to_i}" when "<=" sql = "#{db_table}.#{db_field} <= #{value.first.to_i}" when "o" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" when "c" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id" when ">t-" sql = date_range_clause(db_table, db_field, - value.first.to_i, 0) when "t+" sql = date_range_clause(db_table, db_field, value.first.to_i, nil) when " '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)]) end if to s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)]) end s.join(' AND ') end end ================================================ FILE: app/models/quote.rb ================================================ class Quote < ActiveRecord::Base belongs_to :user def self.random Quote.find :first, :offset => rand(Quote.count) end end ================================================ FILE: app/models/reputation.rb ================================================ class Reputation < ActiveRecord::Base belongs_to :user belongs_to :project VARIATION_SELF_BIAS = 1 VARIATION_SCALE_BIAS = 2 def self.calculate_all calculate(VARIATION_SELF_BIAS) calculate(VARIATION_SCALE_BIAS) end def self.calculate(variation) User.active.each do |user| case variation when VARIATION_SELF_BIAS calculate_moving_average(variation,user.id) when VARIATION_SCALE_BIAS calculate_moving_average(variation,user.id) end end end #calculates a running weighted average of the reputation index #more recent averages are weighed more heavily. def self.calculate_moving_average(variation,user_id) User.find(user_id).projects.all_roots.each do |project| average = bias_moving_average(variation,user_id,project.id) create_or_update(variation,user_id,average,project.id) unless average.nil? end #Platform-wide average project_id = 0 average = bias_moving_average(variation,user_id,project_id) create_or_update(variation,user_id,average,project_id) unless average.nil? end def self.create_or_update(variation,user_id,average,project_id) reputation = Reputation.first(:conditions => {:reputation_type => variation, :user_id => user_id, :project_id => project_id}) if !reputation.nil? reputation.value = average reputation.save else Reputation.create :reputation_type => variation, :user_id => user_id, :project_id => project_id, :value => average end end #Calculates a moving average for an assessment bias (in retrospectives that have their root project: project_id) #When project_id is 0, moving average is calculated across all projects def self.bias_moving_average(variation,user_id,project_id) rr_variation = nil case variation when VARIATION_SELF_BIAS rr_variation = RetroRating::SELF_BIAS when VARIATION_SCALE_BIAS rr_variation = RetroRating::SCALE_BIAS end score_total = 0 weight_total = 0 counter = Setting::LENGTH_OF_MOVING_AVERAGE RetroRating.all(:conditions => {:ratee_id => user_id, :rater_id => rr_variation}, :include => :retro, :order => "updated_at DESC").each do |rr| next if rr.retro.project.root.id != project_id && project_id != 0 weight = rr.retro.total_points * (counter.to_f/Setting::LENGTH_OF_MOVING_AVERAGE) weight_total += weight score_total += rr.score * weight counter += -1 if counter > 1 end weight_total == 0 ? nil : score_total / weight_total end end ================================================ FILE: app/models/retro.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Retro < ActiveRecord::Base include ActionController::UrlWriter include ActionView::Helpers STATUS_INPROGRESS = 1 STATUS_COMPLETE = 2 #is closed, but credits haven't been distributed yet STATUS_DISTRIBUTED = 3 #credits have been distributed STATUS_DISPUTED = 9 NOT_STARTED_ID = -1 #is for issues that haven't been started yet NOT_NEEDED_ID = -2 #is for issues that don't need a retrospective b/c only one person worked on them (e.g gifts) NOT_ENABLED_ID = -5 #is for issues that don't need a retrospective b/c credits are disabled for their workstream # # The following two statuses are for issues that aren't part of a # retrospective but whose credits are given when credits are # distributed in the next retrospective # NOT_GIVEN_AND_NOT_PART_OF_RETRO = -3 GIVEN_BUT_NOT_PART_OF_RETRO = -4 belongs_to :project has_many :issues has_many :journals, :through => :issues has_many :issue_votes, :through => :issues has_many :retro_ratings has_many :credit_disributions #Sets the from_date according to earliest updated issue in retrospective def set_from_date from_date = issues.first(:order => "updated_at ASC").updated_at self.save end def ended? return status_id == STATUS_COMPLETE || status_id == STATUS_DISTRIBUTED end #For closed retrospectives def distribute_credits return unless status_id == STATUS_COMPLETE return if retro_ratings.length == 0 total_dollars = 0 #Total dollar amount for retrospsective issues.each do |issue| total_dollars+= issue.dollar_amount end retro_ratings.find_all{|retro_rating| retro_rating.rater_id == -2}.each do |rr| amount = rr.score * total_dollars / 100 CreditDistribution.create :user_id => rr.ratee_id, :project_id => project_id, :retro_id => rr.retro_id, :amount => amount unless amount == 0 end # # Distribute the hourly credits # They weren't a part of this retrospective but are distributed w/ # the other credits in this retrospective. # hourly_issues = project.issues.find(:all, :conditions => ["retro_id=? AND hourly_type_id IS NOT NULL", Retro::NOT_GIVEN_AND_NOT_PART_OF_RETRO]) hourly_issues.each do |hourly| hourly.give_credits end project.issues.update_all("retro_id=#{Retro::GIVEN_BUT_NOT_PART_OF_RETRO}", "retro_id=#{Retro::NOT_GIVEN_AND_NOT_PART_OF_RETRO} AND hourly_type_id IS NOT NULL") self.status_id = STATUS_DISTRIBUTED self.save end def close return unless status_id == STATUS_INPROGRESS calculate_ratings announce_close self.status_id = STATUS_COMPLETE self.save self.distribute_credits end def calculate_ratings @user_hash = Hash.new @user_final = Hash.new #final distribution @user_self = Hash.new #self assessment @user_total_bias = Hash.new #total bias @confidence_hash = Hash.new @confidence_array = [] #stores confidence that each rater voted with RetroRating.delete_all :rater_id => RetroRating::TEAM_AVERAGE , :retro_id => self.id RetroRating.delete_all :rater_id => RetroRating::FINAL_AVERAGE , :retro_id => self.id RetroRating.delete_all :rater_id => RetroRating::SELF_BIAS , :retro_id => self.id RetroRating.delete_all :rater_id => RetroRating::SCALE_BIAS , :retro_id => self.id return if retro_ratings.length == 0 total_raters = retro_ratings.group_by{|retro_rating| retro_rating.rater_id}.length retro_ratings.group_by{|retro_rating| retro_rating.ratee_id}.keys.each do |user_id| next if user_id < 0; @user_hash[user_id] = [] @confidence_hash[user_id] = 0 @confidence_array[user_id] = 0 end team_confidence_total = 0 retro_ratings.each do |rr| next if rr.rater_id < 0; @user_hash[rr.ratee_id].push(rr.score * rr.confidence) unless ((rr.ratee_id == rr.rater_id) && (total_raters > 1)) @confidence_hash[rr.ratee_id] += rr.confidence unless ((rr.ratee_id == rr.rater_id) && (total_raters > 1)) if (rr.ratee_id == rr.rater_id) @user_self[rr.ratee_id] = rr.score @confidence_array[rr.rater_id] = rr.confidence @user_total_bias[rr.rater_id] = 0 #initializing for later end end #team averages team_average_total = 0 @user_hash.keys.each do |user_id| score = @user_hash[user_id].length == 0 ? 0 : @user_hash[user_id].sum.to_f / @confidence_hash[user_id] RetroRating.create :rater_id => RetroRating::TEAM_AVERAGE, :ratee_id => user_id, :score => score, :retro_id => self.id, :confidence => @confidence_array[user_id] team_average_total = team_average_total + score end #final distribution and self bias @user_hash.keys.each do |user_id| score = @user_hash[user_id].length == 0 ? 0 : @user_hash[user_id].sum.to_f / @confidence_hash[user_id] score = score * 100 / team_average_total self_bias = (@user_self[user_id] - score) * @confidence_array[user_id] / 100 unless @user_self[user_id].nil? || @user_self[user_id].nan? @user_final[user_id] = score RetroRating.create :rater_id => RetroRating::FINAL_AVERAGE, :ratee_id => user_id, :score => score, :retro_id => self.id, :confidence => @confidence_array[user_id] RetroRating.create :rater_id => RetroRating::SELF_BIAS, :ratee_id => user_id, :score => self_bias, :retro_id => self.id, :confidence => @confidence_array[user_id] unless self_bias.nil? || self_bias.nan? end #total bias points retro_ratings.each do |rr| next if rr.rater_id < 0; @user_total_bias[rr.rater_id] += (rr.score - @user_final[rr.ratee_id]).abs end @user_total_bias.keys.each do |user_id| RetroRating.create :rater_id => RetroRating::SCALE_BIAS, :ratee_id => user_id, :score => @user_total_bias[user_id] * @confidence_array[user_id] / 100, :retro_id => self.id, :confidence => @confidence_array[user_id] unless @user_total_bias[user_id].nan? end end #Sends notification to everyone in the retrospective that it's starting def announce_start @users = {} issues.each do |issue| @users.store issue.author_id, 1 unless @users.has_key? issue.author_id @users.store issue.assigned_to_id, 1 unless @users.has_key? issue.assigned_to_id issue.journals.each do |journal| @users.store journal.user_id, 1 unless @users.has_key? journal.user_id end issue.issue_votes.each do |iv| @users.store iv.user_id, 1 unless @users.has_key? iv.user_id end end admin = User.find(:first,:conditions => {:login => "admin"}) @users.keys.each do |user_id| Notification.create :recipient_id => user_id, :variation => 'retro_started', :params => {:project => project}, :sender_id => admin.id, :source_type => "Retro", :source_id => self.id end end #Sends notification to everyone in the retrospective that it's ended def announce_close @users = Hash.new issue_votes.each do |issue_vote| @users[issue_vote.user_id] = 1 if issue_vote.vote_type == IssueVote::JOIN_VOTE_TYPE end admin = User.find(:first,:conditions => {:login => "admin"}) @users.keys.each do |user_id| Notification.create :recipient_id => user_id, :variation => 'retro_ended', :params => {:project => project}, :sender_id => admin.id, :source_type => "Retro", :source_id => self.id end end #True when all team members in a retrospective have participated def all_in? rater_group = retro_ratings.group_by {|retro_rating| retro_rating.rater_id} ratee_group = retro_ratings.group_by {|retro_rating| retro_rating.ratee_id} return (ratee_group.length <= rater_group.length) && (rater_group.length != 0) end end ================================================ FILE: app/models/retro_rating.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class RetroRating < ActiveRecord::Base TEAM_AVERAGE = -1 #retro ratings with a rater id of -1 represent the team average for the ratee FINAL_AVERAGE = -2 #final distribution for the ratee SELF_BIAS = -3 #difference between self assesment and final average as a percentage of final SCALE_BIAS = -4 #sum of differences in percentage points between user's assessment of self and others, and final average belongs_to :retro belongs_to :rater, :class_name => 'User', :foreign_key => 'rater_id' belongs_to :ratee, :class_name => 'User', :foreign_key => 'ratee_id' named_scope :for_project, lambda { |project_id| { :joins => "JOIN retros ON retro_id = retros.id", :conditions => "retros.project_id = #{project_id}" } } end ================================================ FILE: app/models/role.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Role < ActiveRecord::Base # Scopes LEVEL_PLATFORM = 0 LEVEL_ENTERPRISE = 1 LEVEL_PROJECT = 2 # Built-in roles BUILTIN_NON_MEMBER = 1 #scope platform BUILTIN_ANONYMOUS = 2 #scope platform BUILTIN_ADMINISTRATOR = 3 #scope enterprise BUILTIN_CORE_MEMBER = 4 #scope enterprise BUILTIN_CONTRIBUTOR = 5 #scope enterprise BUILTIN_ACTIVE = 7 #scope project BUILTIN_MEMBER = 8 #scope enterprise BUILTIN_BOARD = 9 #scope enterprise BUILTIN_CLEARANCE = 10 #scope project named_scope :givable, { :conditions => "builtin = 0", :order => 'position' } named_scope :builtin, lambda { |*args| compare = 'not' if args.first == true { :conditions => "#{compare} builtin = 0" } } before_destroy :check_deletable has_many :workflows, :dependent => :delete_all do def copy(source_role) Workflow.copy(nil, source_role, nil, proxy_owner) end end has_many :member_roles, :dependent => :destroy has_many :members, :through => :member_roles acts_as_list serialize :permissions, Array validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 validates_format_of :name, :with => /^[\w\s\'\-]*$/i def permissions read_attribute(:permissions) || [] end def permissions=(perms) perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms write_attribute(:permissions, perms) end def add_permission!(*perms) self.permissions = [] unless permissions.is_a?(Array) permissions_will_change! perms.each do |p| p = p.to_sym permissions << p unless permissions.include?(p) end save! end def remove_permission!(*perms) return unless permissions.is_a?(Array) permissions_will_change! perms.each { |p| permissions.delete(p.to_sym) } save! end # Returns true if the role has the given permission def has_permission?(perm) !permissions.nil? && permissions.include?(perm.to_sym) end def <=>(role) role ? position <=> role.position : -1 end def to_s name end # Return true if the role is a builtin role def builtin? self.builtin != 0 end # Return true if the role belongs to the community in any way def community_member? builtin == BUILTIN_CONTRIBUTOR || builtin == BUILTIN_CORE_MEMBER || builtin == BUILTIN_MEMBER || builtin == BUILTIN_ADMINISTRATOR || builtin == BUILTIN_ACTIVE || builtin == BUILTIN_BOARD || builtin == BUILTIN_CLEARANCE end # Return true if the role belongs to the enterprise (i.e. contributor, member, coreateam, admin, or board) def enterprise_member? level == LEVEL_ENTERPRISE end # Return true if the role belongs to the platform (i.e. anonymous, or non member) def platform_member? level == LEVEL_PLATFORM end # Return true if the role is a binding member role def binding_member? builtin == BUILTIN_CORE_MEMBER || builtin == BUILTIN_MEMBER || builtin == BUILTIN_ADMINISTRATOR end # Return true if the role is admin def admin? builtin == BUILTIN_ADMINISTRATOR end # Return true if the role is a project core team member def core_member? builtin == BUILTIN_CORE_MEMBER end # Return true if the role is a project contributor def contributor? builtin == BUILTIN_CONTRIBUTOR end # Return true if the role is a project contributor def member? builtin == BUILTIN_MEMBER end # Return true if the role is active def active? builtin == BUILTIN_ACTIVE end # Return true if the role is a clearance def clearance? builtin == BUILTIN_CLEARANCE end # Return true if role is allowed to do the specified action # action can be: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a permission Symbol (eg. :edit_project) def allowed_to?(action) if action.is_a? Hash allowed_actions.include? "#{action[:controller]}/#{action[:action]}" else allowed_permissions.include? action end end # Return all the permissions that can be given to the role def setable_permissions setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS setable_permissions end # Find all the roles that can be given to a project member def self.find_all_givable(level) find(:all, :conditions => {:level => level}, :order => 'position') end # Return the builtin 'non member' role def self.non_member find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.') end # Return the builtin 'anonymous' role def self.anonymous find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.') end # Return the builtin 'administrator' role def self.administrator find(:first, :conditions => {:builtin => BUILTIN_ADMINISTRATOR}) || raise('Missing Administrator builtin role.') end # Return the builtin 'board' role def self.board find(:first, :conditions => {:builtin => BUILTIN_BOARD}) || raise('Missing Board builtin role.') end # Return the builtin 'contributor' role def self.contributor find(:first, :conditions => {:builtin => BUILTIN_CONTRIBUTOR}) || raise('Missing contributor builtin role.') end # Return the builtin 'core member' role def self.core_member find(:first, :conditions => {:builtin => BUILTIN_CORE_MEMBER}) || raise('Missing core member builtin role.') end # Return the builtin 'member' role def self.member find(:first, :conditions => {:builtin => BUILTIN_MEMBER}) || raise('Missing member builtin role.') end # Return the builtin 'founder' role def self.founder find(:first, :conditions => {:builtin => BUILTIN_FOUNDER}) || raise('Missing founder builtin role.') end # Return the builtin 'clearance' role def self.clearance find(:first, :conditions => {:builtin => BUILTIN_CLEARANCE}) || raise('Missing clearance builtin role.') end # Return the builtin 'active' role def self.active find(:first, :conditions => {:builtin => BUILTIN_ACTIVE}) || raise('Missing active builtin role.') end private def allowed_permissions @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} end def allowed_actions @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten end def check_deletable raise "Can't delete role" if members.any? raise "Can't delete builtin role" if builtin? end end ================================================ FILE: app/models/setting.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Setting < ActiveRecord::Base APP_TITLE = "Bettermeans.com" TEXT_FORMATTING = "textile" MAXIMUM_CONCURRENT_REQUESTS = 10 #Maximum issues same pereson can own at the same time per workstream PAY_SCALES = {'Scale 1' => 100, 'Scale 2' => 50, 'Scale 3' => 20} PAY_SCALES_DEFAULT = 100 DEFAULT_RETROSPECTIVE_LENGTH = 3 #Length in days for which a retrospective is open RETRO_CREDIT_THRESHOLD = 3000 # credit threshold for retrospective to start. RETRO_DAY_THRESHOLD = 21 # day threshold for retrospective to start (days since last retrospective ended) DAY_FOR_CREDIT_DISTRIBUTION = "Saturday" NUMBER_OF_STARTABLE_PRIORITY_TIERS = 3 #number of highest tiers that are startable DAYS_FOR_ACTIVE_MEMBERSHIP = 14 #If a member does any activity on a project in the last X days, they're considered active DAYS_FOR_LATEST_NEWS = 45 #Number of days before a news item expires DAYS_FOR_RECENT_PROJECTS = 60 #Number of days workstreams appear if they were part of recent activity ACTIVITY_LINE_LENGTH = 90 #number of days for activity sparklines ACTIVITY_STREAM_LENGTH = 40 #number of actions to show before paginating WORKSTREAM_LOCK_THRESHOLD = 30 #number of days overdue before workstreams are locked GLOBAL_OVERUSE_THRESHOLD = 7 #number of days before which a global flash message is issued saying that user is over limits #Factor by which dollars per point is multiplies e.g. a 5 point issue is worth $(POINT_FACTOR[5] * dpp) POINT_FACTOR = [0.2,1,2,4,6,9,12] #Reverse lookup. Converts credits to points CREDITS_TO_POINTS = [0,1,2,3,3,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,9,10,10,10,10,10,11,11,11,11,11,11,12,12,12,12,12,12,13,13,13,13,13,13,13,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,20]; #Percentage of share that need to agree on a share-majority motion before it passes SHARE_MAJORIY_MOTION_RATIO = 0.666666666 MOTIONS = { Motion::VARIATION_GENERAL => { "Title" => "General Motion", "Binding" => Motion::BINDING_MEMBER, "Visible" => Motion::VISIBLE_USER, "Type" => Motion::TYPE_MAJORITY, "Days" => 3 }, Motion::VARIATION_EXTRAORDINARY => { "Title" => "Extraordinary Motion", "Binding" => Motion::BINDING_USER, "Visible" => Motion::VISIBLE_USER, "Type" => Motion::TYPE_SHARE, "Days" => 10 }, Motion::VARIATION_NEW_MEMBER => { "Title" => "Motion to elect a new Member", "Binding" => Motion::BINDING_CORE, "Visible" => Motion::VISIBLE_CONTRIBUTER, "Type" => Motion::TYPE_CONSENSUS, "Days" => 5 }, Motion::VARIATION_FIRE_MEMBER => { "Title" => "Motion to remove an existing Member", "Binding" => Motion::BINDING_CORE, "Visible" => Motion::VISIBLE_CONTRIBUTER, "Type" => Motion::TYPE_CONSENSUS, "Days" => 5 }, Motion::VARIATION_NEW_CORE => { "Title" => "Motion to elect a new Core Team Member", "Binding" => Motion::BINDING_MEMBER, "Visible" => Motion::VISIBLE_CONTRIBUTER, "Type" => Motion::TYPE_CONSENSUS, "Days" => 5 }, Motion::VARIATION_FIRE_CORE => { "Title" => "Motion to remove an existing Core Team Member", "Binding" => Motion::BINDING_MEMBER, "Visible" => Motion::VISIBLE_CONTRIBUTER, "Type" => Motion::TYPE_CONSENSUS, "Days" => 5 }, Motion::VARIATION_BOARD_PRIVATE => { "Title" => "Closed Board Motion", "Binding" => Motion::BINDING_BOARD, "Visible" => Motion::VISIBLE_BOARD, "Type" => Motion::TYPE_CONSENSUS, "Days" => 5 } } LAZY_MAJORITY_LENGTH = 3 #number of days before a lazy majority vote is counted LAZY_MAJORITY_NO_ACTIVITY_LENGTH = 1 #number of days an item needs to have no activity on before a lazy majority move is attempted on it #Reputation calculation constants #lenght of window for moving averages for reputation index average calculation. #example: if this number is 20, the average before last will be weighed at 19/20, the one before that at 18/20, all scores past the 20th most recent scores will be weighed at 1/20 of their value in the totaly average LENGTH_OF_MOVING_AVERAGE = 20 DATE_FORMATS = [ '%Y-%m-%d', '%d/%m/%Y', '%d.%m.%Y', '%d-%m-%Y', '%m/%d/%Y', '%d %b %Y', '%d %B %Y', '%b %d, %Y', '%B %d, %Y' ] TIME_FORMATS = [ '%H:%M', '%I:%M %p' ] ENCODINGS = %w(US-ASCII windows-1250 windows-1251 windows-1252 windows-1253 windows-1254 windows-1255 windows-1256 windows-1257 windows-1258 windows-31j ISO-2022-JP ISO-2022-KR ISO-8859-1 ISO-8859-2 ISO-8859-3 ISO-8859-4 ISO-8859-5 ISO-8859-6 ISO-8859-7 ISO-8859-8 ISO-8859-9 ISO-8859-13 ISO-8859-15 KOI8-R UTF-8 UTF-16 UTF-16BE UTF-16LE EUC-JP Shift_JIS GB18030 GBK ISCII91 EUC-KR Big5 Big5-HKSCS TIS-620) cattr_accessor :available_settings @@available_settings = YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml")) Redmine::Plugin.all.each do |plugin| next unless plugin.settings @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true} end validates_uniqueness_of :name validates_inclusion_of :name, :in => @@available_settings.keys validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' } # Hash used to cache setting values @cached_settings = {} @cached_cleared_on = Time.now before_save :serialize_value def value @value ||= read_attribute(:value) # Unserialize serialized settings @value = YAML::load(@value) if @@available_settings[name]['serialized'] && @value.is_a?(String) @value = @value.to_sym if @@available_settings[name]['format'] == 'symbol' && !@value.blank? @value end def value=(value) @value = value end def serialize_value @value = @value.to_yaml if @value && @@available_settings[name]['serialized'] write_attribute(:value, @value.to_s) end # Returns the value of the setting named name def self.[](name) v = @cached_settings[name] v ? v : (@cached_settings[name] = find_or_default(name).value) end def self.[]=(name, v) setting = find_or_default(name) setting.value = (v ? v : "") @cached_settings[name] = nil setting.save setting.value end # Defines getter and setter for each setting # Then setting values can be read using: Setting.some_setting_name # or set using Setting.some_setting_name = "some value" @@available_settings.each do |name, params| src = <<-END_SRC def self.#{name} self[:#{name}] end def self.#{name}? self[:#{name}].to_i > 0 end def self.#{name}=(value) self[:#{name}] = value end END_SRC class_eval src, __FILE__, __LINE__ end # Helper that returns an array based on per_page_options setting def self.per_page_options_array per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort end def self.openid? Object.const_defined?(:OpenID) && self[:openid].to_i > 0 end # Checks if settings have changed since the values were read # and clears the cache hash if it's the case # Called once per request def self.check_cache settings_updated_at = Setting.maximum(:updated_at) if settings_updated_at && @cached_cleared_on <= settings_updated_at @cached_settings.clear @cached_cleared_on = Time.now end end private # Returns the Setting instance for the setting named name # (record found in database or new record with default value) def self.find_or_default(name) name = name.to_s raise "There's no setting named #{name}" unless @@available_settings.has_key?(name) if @@available_settings.has_key? name setting = new(:name => name) setting.value = @@available_settings[name]['default'] end setting ||= find_by_name(name) end end ================================================ FILE: app/models/share.rb ================================================ class Share < ActiveRecord::Base default_value_for :issued_on do Time.now end #Constants VARIATION_FOUNDER = 1 #issue when enterprise starts. don't expire VARIATION_CREDIT = 2 #issued when credit is issued, expire belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id' belongs_to :project named_scope :for_project, lambda { |*project_id| {:conditions => {:project_id => project_id}} } def self.set_expiration(owner,project,amount,expiration_date) remaining_amount = amount Share.find(:all,:conditions => {:expires => nil, :project_id => project.id, :owner_id => owner.id, :variation => VARIATION_CREDIT}, :order => 'issued_on ASC').each do |share| if share.amount <= remaining_amount share.expires = expiration_date remaining_amount = remaining_amount - share.amount share.save else #More shares in this lot than we are expiring unexpired_amount = Credit.round(share.amount - remaining_amount) #modify the amount for this lot to the expiring amount after rounding share.amount = share.amount - unexpired_amount share.expires = expiration_date share.save #create a new lot with the remaining unexpired shares Share.create! :amount => unexpired_amount, :owner => share.owner, :project => share.project, :issued_on => share.issued_on break; end end end end ================================================ FILE: app/models/todo.rb ================================================ class Todo < ActiveRecord::Base belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id' belongs_to :issue after_save :update_issue_timestamp def update_issue_timestamp issue.updated_at = DateTime.now issue.save end end ================================================ FILE: app/models/token.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class Token < ActiveRecord::Base belongs_to :user validates_uniqueness_of :value before_create :delete_previous_tokens @@validity_time = 30.day def before_create self.value = Token.generate_token_value end # Return true if token has expired def expired? return Time.now > self.created_at + @@validity_time end # Delete all expired tokens def self.destroy_expired Token.delete_all ["action <> 'feeds' AND created_at < ?", Time.now - @@validity_time] end private def self.generate_token_value ActiveSupport::SecureRandom.hex(20) end # Removes obsolete tokens (same user and action) def delete_previous_tokens if user Token.delete_all(['user_id = ? AND action = ?', user.id, action]) end end end ================================================ FILE: app/models/track.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Track < ActiveRecord::Base belongs_to :user LOGIN = 1 reportable :daily_logins, :aggregation => :count, :limit => 14, :conditions => ["code = ?", LOGIN] reportable :weekly_logins, :aggregation => :count, :grouping => :week, :limit => 20, :conditions => ["code = ?", LOGIN] def self.log(code, ip="") Track.send_later(:create, {:user_id => User.current.id, :code => code, :ip => ip}) end end ================================================ FILE: app/models/tracker.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Tracker < ActiveRecord::Base before_destroy :check_integrity has_many :issues has_many :workflows, :dependent => :delete_all do def copy(source_tracker) Workflow.copy(source_tracker, nil, proxy_owner, nil) end end has_and_belongs_to_many :projects acts_as_list validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 validates_format_of :name, :with => /^[\w\s\'\-]*$/i def to_s; name end def <=>(tracker) name <=> tracker.name end def self.all find(:all, :order => 'position') end #All trackers except the ones that apply to the credits module def self.no_credits find(:all, :conditions => {:for_credits_module => false}, :order => 'position') end def gift? name == l(:default_issue_tracker_gift) end def expense? name == l(:default_issue_tracker_expense) end def recurring? name == l(:default_issue_tracker_recurring) end def hourly? name == l(:default_issue_tracker_hourly) end def feature? name == l(:default_issue_tracker_feature) end def bug? name == l(:default_issue_tracker_bug) end def chore? name == l(:default_issue_tracker_chore) end # Returns an array of IssueStatus that are used # in the tracker's workflows def issue_statuses if @issue_statuses return @issue_statuses elsif new_record? return [] end ids = Workflow. connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{Workflow.table_name} WHERE tracker_id = #{id}"). flatten. uniq @issue_statuses = IssueStatus.find_all_by_id(ids).sort end private def check_integrity raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id]) end end ================================================ FILE: app/models/user.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require "digest/sha1" class User < ActiveRecord::Base # TODO: change `mail` to `email` # Account statuses STATUS_ANONYMOUS = 0 STATUS_ACTIVE = 1 STATUS_REGISTERED = 2 STATUS_LOCKED = 3 STATUS_CANCELED = 4 USER_FORMATS = { :firstname_lastname => '#{firstname} #{lastname}', :firstname => '#{firstname}', :lastname_firstname => '#{lastname} #{firstname}', :lastname_coma_firstname => '#{lastname}, #{firstname}', :username => '#{login}' } has_many :members, :foreign_key => 'user_id', :dependent => :destroy has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name" has_many :core_memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Role.table_name}.builtin=#{Role::BUILTIN_CORE_MEMBER}", :order => "#{Project.table_name}.name" has_many :active_memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Role.table_name}.builtin=#{Role::BUILTIN_ACTIVE}", :order => "#{Project.table_name}.name" has_many :projects, :through => :memberships has_many :owned_projects, :class_name => 'Project', :foreign_key => 'owner_id', :include => [:all_members] has_many :invitations has_many :activity_streams, :foreign_key => 'actor_id', :dependent => :delete_all # TODO: re-order relations has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'" belongs_to :auth_source belongs_to :plan has_many :notifications, :foreign_key => 'recipient_id', :dependent => :delete_all has_many :shares, :foreign_key => :owner_id, :dependent => :nullify has_many :credits, :foreign_key => :owner_id, :dependent => :delete_all has_many :issue_votes, :dependent => :delete_all has_many :authored_todos, :class_name => 'Todo', :foreign_key => 'author_id', :dependent => :nullify has_many :owned_todos, :class_name => 'Todo', :foreign_key => 'owner_id', :dependent => :nullify has_many :outgoing_ratings, :class_name => 'RetroRating', :foreign_key => 'rater_id' has_many :incoming_ratings, :class_name => 'RetroRating', :foreign_key => 'ratee_id' has_many :credit_disributions has_many :reputations, :dependent => :delete_all has_many :help_sections has_many :tokens # Active non-anonymous users scope # TODO: change this to use array interpolation syntax named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}" # TODO: double check that all the columns in the :order below are indexed named_scope :like, lambda {|q| s = "%#{q.to_s.strip.downcase}%" {:conditions => ["LOWER(login) LIKE :s OR LOWER(firstname) LIKE :s OR LOWER(lastname) LIKE :s OR LOWER(mail) LIKE :s", {:s => s}], :order => 'type, login, lastname, firstname, mail' } } has_private_messages :class_name => "Mail" attr_accessor :password, :password_confirmation attr_accessor :last_before_login_on # Prevents unauthorized assignments # TODO: password, password_confirmation should be mass assignable, and maybe login # this would be better as attr_accessor attr_protected :login, :admin, :password, :password_confirmation, :hashed_password # BUGBUG: seems to be some bug here where it allows a nil email validates_presence_of :login, :firstname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? } validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false # Login must contain letters, numbers, underscores only validates_format_of :login, :with => /^[a-z0-9_@\.]*$/i validates_length_of :login, :maximum => 30 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i validates_length_of :firstname, :lastname, :maximum => 30 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_length_of :mail, :maximum => 60, :allow_nil => true validates_confirmation_of :password, :allow_nil => true reportable :daily_registrations, :aggregation => :count, :limit => 14 reportable :weekly_registrations, :aggregation => :count, :grouping => :week, :limit => 20 # =============== # = CSV support = # =============== comma do # implicitly named :default id login firstname lastname mail last_login_on created_at updated_at plan_id trial_expires_on active_subscription end def <=>(user) if self.class.name == user.class.name self.to_s.downcase <=> user.to_s.downcase else # groups after users user.class.name <=> self.class.name end end def before_create self.mail_notification = false self.login = self.login.downcase true end def before_save # update hashed_password if password was set self.hashed_password = User.hash_password(self.password) if self.password self.mail_hash = Digest::MD5.hexdigest(self.mail) unless mail.nil? end def after_create activate_invitations User.send_later(:create_recurly_account,self.id) end def activate_invitations Invitation.all(:conditions => {:new_mail => self.mail}).each do |invite| invite.accept end end def after_update User.send_later(:update_recurly_account,self.id) end def self.create_recurly_account(id) @user = User.find(id) begin @account = Recurly::Account.find(@user.id) rescue ActiveResource::ResourceNotFound @account = Recurly::Account.create( :account_code => @user.id, :first_name => @user.firstname, :last_name => @user.lastname, :email => @user.mail, :username => @user.login) end end def self.update_recurly_account(id) @user = User.find(id) begin @account = Recurly::Account.find(@user.id) rescue ActiveResource::ResourceNotFound @account = User.create_recurly_account(id) end @account.account_code = @user.id @account.first_name = @user.firstname @account.last_name = @user.lastname @account.email = @user.mail @account.username = @user.login @account.save @result_object = Recurly::Account.find(@account.account_code) end def save_billing(cc,ccverify,ip) User.send_later(:update_recurly_billing,self.id,cc,ccverify,ip) end def self.update_recurly_billing(id,cc,ccverify,ip) @user = User.find(id) begin @account = Recurly::Account.find(@user.id) rescue ActiveResource::ResourceNotFound @account = User.create_recurly_account(id) end cc.gsub!(/[^0-9]/,'') if cc if cc && cc.length > 14 @account.billing_info = Recurly::BillingInfo.create( :account_code => @account.account_code, :first_name => @account.first_name, :last_name => @account.last_name, :address1 => @user.b_address1, :zip => @user.b_zip, :country => @user.b_country, :city => "none", :state => "none", :phone => @user.b_phone, :ip_address => ip, :credit_card => { :number => cc, :year => @user.b_cc_year, :month => @user.b_cc_month, :verification_value => ccverify }) return @account if @account.billing_info.errors @user.b_cc_type = @account.billing_info.credit_card.attributes["type"] @user.b_cc_last_four = "XXXX - " + @account.billing_info.credit_card.attributes["last_four"] + " " + @account.billing_info.credit_card.attributes["type"] @user.save else @account.billing_info = Recurly::BillingInfo.create( :account_code => @account.account_code, :first_name => @account.first_name, :last_name => @account.last_name, :address1 => @user.b_address1, :zip => @user.b_zip, :country => @user.b_country, :city => "none", :state => "none", :phone => @user.b_phone, :ip_address => ip) end return @account end def reload(*args) @name = nil super end def lock_workstreams? (self.usage_over_at && self.usage_over_at.advance(:days => -1 * Setting::WORKSTREAM_LOCK_THRESHOLD) > DateTime.now) || (self.trial_expired_at && self.trial_expired_at.advance(:days => -1 * Setting::WORKSTREAM_LOCK_THRESHOLD) > DateTime.now) end #detects if usage is way over, or trial has expired for a while, and locks out private workstreams belonging to user def lock_workstreams if self.lock_workstreams? self.owned_projects.each {|p| p.lock unless p.is_public?} end end def unlock_workstreams unless self.lock_workstreams? self.owned_projects.each {|p| p.unlock unless p.is_public?} end end def usage_over? self.project_storage_total > self.plan.storage_max || self.private_project_total > self.plan.private_workstream_max || self.private_contributor_total > self.plan.contributor_max end #detects if usage is over, and sets date of going over def update_usage_over is_over = self.usage_over? self.lock_workstreams if is_over if is_over && !self.usage_over_at logger.info { "we are over" } Notification.create :recipient_id => self.id, :variation => 'usage_over', :sender_id => User.sysadmin.id, :source_id => self.id, :source_type => "User" self.update_attribute(:usage_over_at, DateTime.now) end if !is_over && self.usage_over_at logger.info { "we are not over" } Notification.delete_all(:variation => 'usage_over', :source_id => self.id) self.update_attribute(:usage_over_at, nil) self.unlock_workstreams end end #detects if trial expired, and sets date of trial expiring def update_trial_expiration return if self.plan.free? if !self.trial_expires_on if self.trial_expired_at self.update_attribute(:trial_expired_at, nil) self.unlock_workstreams end return end if self.trial_expired_at self.lock_workstreams return end if DateTime.now > self.trial_expires_on Notification.create :recipient_id => self.id, :variation => 'trial_expired', :sender_id => User.sysadmin.id, :source_id => self.id, :source_type => "User" self.update_attribute(:trial_expired_at, DateTime.now) end end def identity_url=(url) if url.blank? write_attribute(:identity_url, '') else begin write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) rescue OpenIdAuthentication::InvalidOpenId # Invlaid url, don't save end end self.read_attribute(:identity_url) end # Returns the user that matches provided login and password, or nil def self.try_to_login(login, password) # Make sure no one can sign in with an empty password return nil if password.to_s.empty? user = find(:first, :conditions => ["login=?", login.downcase]) if user if user.auth_source # user has an external authentication method return nil unless user.auth_source.authenticate(login, password) else # authentication with local password return nil unless User.hash_password(password) == user.hashed_password end else # user is not yet registered, try to authenticate with available sources attrs = AuthSource.authenticate(login, password) if attrs user = new(*attrs) user.login = login user.language = Setting.default_language if user.save user.reload end end end user.update_attribute(:last_login_on, Time.now) if user && !user.new_record? user rescue => text raise text end # Returns the user who matches the given autologin +key+ or nil def self.try_to_autologin(key) tokens = Token.find_all_by_action_and_value('autologin', key) # Make sure there's only 1 token that matches the key if tokens.size == 1 token = tokens.first if (token.created_at > Setting.autologin.to_i.day.ago) && token.user && token.user.active? token.user.update_attribute(:last_login_on, Time.now) token.user end end end # Return user's full name for display def name(formatter = nil) if formatter eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"').strip else @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"').strip end end def active? self.status == STATUS_ACTIVE end def reactivate # TODO: don't use update_attribute, as it bypasses validations self.update_attribute(:status, User::STATUS_ACTIVE) newmail = [] # TODO: get rid of this logic self.mail.split('.').each do |s| break if s == 'canceled' newmail.push(s) end newmail = newmail.join(".") # TODO: don't use update_attribute, as it bypasses validations self.update_attribute(:mail, newmail) if self.mail != newmail end def registered? self.status == STATUS_REGISTERED end def lock # TODO: don't use update_attribute, as it bypasses validations self.update_attribute(:status, STATUS_LOCKED) end def cancel self.update_attribute(:status, STATUS_CANCELED) # TODO: get rid of this, not sure why we change the email self.update_attribute(:mail, self.mail + ".canceled.#{rand(1000)}") end def cancel_account! cancel end def canceled? status == STATUS_CANCELED end def locked? self.status == STATUS_LOCKED end def check_password?(clear_password) User.hash_password(clear_password) == self.hashed_password end # Generate and set a random password. Useful for automated user creation # Based on Token#generate_token_value # def random_password chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a password = '' 40.times { |i| password << chars[rand(chars.size-1)] } self.password = password self.password_confirmation = password self end def pref self.preference ||= UserPreference.new(:user => self) end def time_zone @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone]) end def wants_comments_in_reverse_order? self.pref[:comments_sorting] == 'desc' end # Return user's RSS key (a 40 chars long string), used to access feeds def rss_key token = self.rss_token || Token.create(:user => self, :action => 'feeds') token.value end # Return user's API key (a 40 chars long string), used to access the API def api_key token = self.api_token || Token.create(:user => self, :action => 'api') token.value end # Return an array of project ids for which the user has explicitly turned mail notifications on def notified_projects_ids @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) end def notified_project_ids=(ids) Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? @notified_projects_ids = nil notified_projects_ids end def self.find_by_rss_key(key) token = Token.find_by_value(key) token && token.user.active? ? token.user : nil end def self.find_by_api_key(key) token = Token.find_by_action_and_value('api', key) token && token.user.active? ? token.user : nil end # Makes find_by_mail case-insensitive def self.find_by_mail(mail) find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase]) end # Makes find_by_login case-insensitive def self.find_by_login(login) find(:first, :conditions => ["LOWER(login) = ?", login.to_s.downcase]) end def to_s name end # Returns the current day according to user's time zone def today if time_zone.nil? Date.today else Time.now.in_time_zone(time_zone).to_date end end def logged? true end def anonymous? !logged? end # Return user's roles for project def roles_for_project(child_project) project = child_project.root roles = [] # No role on archived projects return roles unless project && project.active? if logged? # Find project membership membership = memberships.detect {|m| m.project_id == project.id} if membership roles = membership.roles else @role_non_member ||= Role.non_member roles << @role_non_member end else @role_anonymous ||= Role.anonymous roles << @role_anonymous end roles end # Return true if the user is a communitymember of project def community_member_of?(project) !roles_for_project(project.root).detect {|role| role.community_member?}.nil? end # Return true if the user is an enterprise member of project def enterprise_member_of?(project) !roles_for_project(project.root).detect {|role| role.enterprise_member?}.nil? end # Return true if the user is admin of project def admin_of?(project) !roles_for_project(project.root).detect {|role| role.admin?}.nil? end # Return true if the user is a member of project def member_of?(project) !roles_for_project(project.root).detect {|role| role.member?}.nil? end # Return true if the user is a core member of project def core_member_of?(project) !roles_for_project(project.root).detect {|role| role.core_member?}.nil? end # Return true if the user is a contributor of project def contributor_of?(project) !roles_for_project(project.root).detect {|role| role.contributor?}.nil? end # Return true if the user's votes are binding def binding_voter_of?(project) !roles_for_project(project.root).detect {|role| role.binding_member?}.nil? end # Return true if the user's votes are binding for this motion def binding_voter_of_motion?(motion) position_for(motion.project) <= motion.binding_level.to_f end # Return true if the user is allowed to see motion def allowed_to_see_motion?(motion) return true if User.current == User.sysadmin position_for(motion.project) <= motion.visibility_level.to_f end # Return true if the user is a allowed to see project #If root project is public, then we check that user has been given explicit clearance #If root project is private, then all contributors have access def allowed_to_see_project?(project) return true if project.is_public? if project.root.is_public? roles_for_project(project).detect {|role| role.binding_member? || role.clearance?} else roles_for_project(project).detect {|role| role.community_member?} end end # Returns position level for user's role in project's enterprise (the lower number, the higher in heirarchy the user) def position_for(project) roles_for_project(project.root).sort{|x,y| x.position <=> y.position}.first.position end # Return true if the user is allowed to do the specified action on project # action can be: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a permission Symbol (eg. :edit_project) def allowed_to?(action, project, options={}) if project # No action allowed on archived projects except unarchive return false unless project.active? || project.locked? || (action.class.to_s == "Hash" && action[:action] == "unarchive") # No action allowed on disabled modules return false unless project.allows_to?(action) # Admin users are authorized for anything else return true if admin? # #Check if user is a citizen of the enterprise, and the citizen role is allowed to take that action # return true if citizen_of?(project) && Role.citizen.allowed_to?(action) roles = roles_for_project(project) return false unless roles roles.detect {|role| role.allowed_to?(action)} && allowed_to_see_project?(project) elsif options[:global] # Admin users are always authorized return true if admin? # authorize if user has at least one role that has this permission roles = memberships.collect {|m| m.roles}.flatten.uniq roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action)) else false end end #Adds current user to core team of project def add_as_core(project, options={}) #Add as core member of current project add_to_project project, Role.core_member drop_from_project(project, Role.contributor) drop_from_project(project, Role.member) end def add_as_member(project, options={}) #Add as core member of current project add_to_project project, Role.member drop_from_project(project, Role.contributor) drop_from_project(project, Role.core_member) end #Adds current user as contributor of project def add_as_contributor(project, options={}) add_to_project project, Role.contributor drop_from_project(project, Role.core_member) drop_from_project(project, Role.member) end #Adds current user as contributor of project if they aren't a binding member def add_as_contributor_if_new(project, options={}) add_as_contributor project unless self.binding_voter_of?(project) end #Adds user to that project as that role def add_to_project(project, role, options={}) project = project.root if role.enterprise_member? m = Member.find(:first, :conditions => {:user_id => id, :project_id => project}) #First we see if user is already a member of this project if m.nil? #User isn't a member let's create a membership member_role = Role.find(:first, :conditions => {:id => role.id}) m = Member.new(:user => self, :roles => [member_role]) p = Project.find(project) result = p.all_members << m else #User is already a member, we just add a role (but make sure role doesn't exist already) MemberRole.create! :member_id => m.id, :role_id => role.id if MemberRole.first(:conditions => {:member_id => m.id, :role_id => role.id}) == nil end end #Drops user from role of that project def drop_from_project(project, role, options={}) m = Member.find(:first, :conditions => {:user_id => id, :project_id => project}) #First we see if user is already a member of this project m.member_roles.each {|r| r.destroy if r.role_id == role.id } unless m.nil? end #Drops current user from core team of project def drop_from_core(project, options={}) drop_from_project project, Role::BUILTIN_CORE_MEMBER end def self.current=(user) @current_user = user end def self.current @current_user ||= User.anonymous end # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only # one anonymous user per database. def self.anonymous anonymous_user = AnonymousUser.find(:first) if anonymous_user.nil? anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) raise 'Unable to create the anonymous user.' if anonymous_user.new_record? end anonymous_user end def self.sysadmin User.find_by_login("admin") end #total owned public projects def public_project_total self.owned_projects.find_all{|p| p.is_public && (p.active? || p.locked?) }.length end def private_project_total self.owned_projects.find_all{|p| !p.is_public && (p.active? || p.locked?) }.length end def public_contributor_total @all_users = [] self.owned_projects.find_all{|p| p.is_public && (p.active? || p.locked?) }.each {|p| @all_users = @all_users | p.all_members.collect{|m| m.user_id}} @all_users.length end def private_contributor_total @all_users = [] self.owned_projects.find_all{|p| !p.is_public && (p.active? || p.locked?) }.each {|p| @all_users = @all_users | p.all_members.collect{|m| m.user_id}} @all_users.length end def project_storage_total self.owned_projects.inject(0){|sum,item| sum + item.storage} end #projects user belongs to but doesnt own def belongs_to_projects self.projects.does_not_belong_to(self.id) end #returns list of recent projects with a max count def recent_projects(max = 10) project_ids = ActivityStream.find_by_sql("SELECT project_id FROM activity_streams WHERE actor_id = #{self.id} AND updated_at > '#{Time.now.advance :days => (Setting::DAYS_FOR_RECENT_PROJECTS * -1)}' GROUP BY project_id ORDER BY MAX(updated_at) DESC LIMIT #{max}").collect {|a| a.project_id}.join(",") return [] unless project_ids.length > 0 Project.find(:all, :conditions => "id in (#{project_ids})") end #returns list of recent items with a max count of 10 def recent_items(max = 10) item_ids = ActivityStream.find_by_sql("SELECT object_id FROM activity_streams WHERE actor_id = #{self.id} AND object_type = 'Issue' AND object_id is not null GROUP BY object_id ORDER BY MAX(updated_at) DESC LIMIT #{max}").collect {|a| a["object_id"]}.join(",") return [] if item_ids.empty? Issue.find(:all, :conditions => "id in (#{item_ids})", :include => [:project, :tracker ], :order => "#{Issue.table_name}.updated_at DESC") end def self.find_available_login(array) array.each do |string| string = string.gsub(/ /,"_").gsub(/'|\"|<|>/,"_") # TODO: this can probably be optimized into a single database query return string unless find_by_login(string) end nil end def delete_autologin_tokens tokens.find_all_by_action('autologin').collect(&:delete) end def activate self.status = STATUS_ACTIVE end protected def validate # Password length validation based on setting if !password.nil? && password.size < Setting.password_min_length.to_i errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) end end private # Return password digest def self.hash_password(clear_password) # TODO: somehow switch this out, SHA is not recommended for passwords Digest::SHA1.hexdigest(clear_password || "") end end class AnonymousUser < User def validate_on_create # There should be only one AnonymousUser in the database errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first) end # Overrides a few properties def logged?; false end def admin; false end def name(*args); I18n.t(:label_user_anonymous) end def mail; nil end def time_zone; nil end def rss_key; nil end def delete_autologin_tokens; nil end end ================================================ FILE: app/models/user_preference.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class UserPreference < ActiveRecord::Base belongs_to :user serialize :others attr_protected :others DEFAULTS = {:no_self_notified=>true, :daily_digest=>true, :no_emails=>false, :comments_sorting=>"asc", :active_only_jumps=>false} # BUGBUG: this initialize won't work consistently # when extending from ActiveRecord initialize doesn't always get called # http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html # better to make this an after_initialize def initialize(attributes = nil) super self.others ||= DEFAULTS end def before_save self.others ||= DEFAULTS end def [](attr_name) if attribute_present? attr_name super else others ? others[attr_name] : nil end end def []=(attr_name, value) if attribute_present? attr_name super else h = read_attribute(:others).dup || DEFAULTS h.update(attr_name => value) write_attribute(:others, h) value end end def comments_sorting; self[:comments_sorting] end def comments_sorting=(order); self[:comments_sorting]=order end end ================================================ FILE: app/models/watcher.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Watcher < ActiveRecord::Base belongs_to :watchable, :polymorphic => true belongs_to :user validates_presence_of :user validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] # Unwatch things that users are no longer allowed to view def self.prune(options={}) if options.has_key?(:user) prune_single_user(options[:user], options) else pruned = 0 User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user| pruned += prune_single_user(user, options) end pruned end end protected def validate errors.add :user_id, :invalid unless user.nil? || user.active? end private def self.prune_single_user(user, options={}) return unless user.is_a?(User) pruned = 0 find(:all, :conditions => {:user_id => user.id}).each do |watcher| next if watcher.watchable.nil? if options.has_key?(:project) next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project] end if watcher.watchable.respond_to?(:visible?) unless watcher.watchable.visible?(user) watcher.destroy pruned += 1 end end end pruned end end ================================================ FILE: app/models/wiki.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class Wiki < ActiveRecord::Base belongs_to :project has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title' has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all acts_as_watchable validates_presence_of :start_page validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/ def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_wiki_pages, project) end # find the page with the given title # if page doesn't exist, return a new page def find_or_new_page(title) title = start_page if title.blank? find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title)) end # find the page with the given title def find_page(title, options = {}) title = start_page if title.blank? title = Wiki.titleize(title) page = pages.find_by_title(title) if !page && !(options[:with_redirect] == false) # search for a redirect redirect = redirects.find_by_title(title) page = find_page(redirect.redirects_to, :with_redirect => false) if redirect end page end # Finds a page by title # The given string can be of one of the forms: "title" or "project:title" # Examples: # Wiki.find_page("bar", project => foo) # Wiki.find_page("foo:bar") def self.find_page(title, options = {}) project = options[:project] if title.to_s =~ %r{^([^\:]+)\:(.*)$} project_identifier, title = $1, $2 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier) end if project && project.wiki page = project.wiki.find_page(title) if page && page.content page end end end # turn a string into a valid page title def self.titleize(title) # replace spaces with _ and remove unwanted caracters title = title.gsub(/\s+/, '_').delete(',./?;|:') if title # upcase the first letter title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title title end end ================================================ FILE: app/models/wiki_content.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'zlib' class WikiContent < ActiveRecord::Base set_locking_column :version belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates_presence_of :text validates_length_of :comments, :maximum => 255, :allow_nil => true acts_as_versioned def visible?(user=User.current) page.visible?(user) end def project page.project end # Returns the mail adresses of users that should be notified def recipients notified = project.notified_users notified.reject! {|user| !visible?(user) || user.pref[:no_emails]} notified.collect(&:mail) end class Version belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id' belongs_to :author, :class_name => '::User', :foreign_key => 'author_id' attr_protected :data acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, :description => :comments, :datetime => :updated_at, :type => 'wiki-page', :url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}} def text=(plain) case Setting.wiki_compression when 'gzip' begin self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) self.compression = 'gzip' rescue self.data = plain self.compression = '' end else self.data = plain self.compression = '' end plain end def text @text ||= case compression when 'gzip' Zlib::Inflate.inflate(data) else # uncompressed data data end end def project page.project end # Returns the previous version or nil def previous @previous ||= WikiContent::Version.find(:first, :order => 'version DESC', :include => :author, :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version]) end end end ================================================ FILE: app/models/wiki_content_observer.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# class WikiContentObserver < ActiveRecord::Observer def after_create(wiki_content) Mailer.deliver_wiki_content_added(wiki_content) if Setting.notified_events.include?('wiki_content_added') end def after_update(wiki_content) if wiki_content.text_changed? Mailer.deliver_wiki_content_updated(wiki_content) if Setting.notified_events.include?('wiki_content_updated') end end end ================================================ FILE: app/models/wiki_page.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# require 'diff' require 'enumerator' class WikiPage < ActiveRecord::Base belongs_to :wiki has_one :content, :class_name => 'WikiContent', :foreign_key => 'page_id', :dependent => :destroy acts_as_attachable :delete_permission => :delete_wiki_pages_attachments acts_as_tree :dependent => :nullify, :order => 'title' acts_as_watchable acts_as_event :title => Proc.new {|o| "#{l(:label_wiki)}: #{o.title}"}, :description => :text, :datetime => :created_at, :url => Proc.new {|o| {:controller => 'wiki', :id => o.wiki.project, :page => o.title}} acts_as_searchable :columns => ['title', 'text'], :include => [{:wiki => :project}, :content], :project_key => "#{Wiki.table_name}.project_id" attr_accessor :redirect_existing_links validates_presence_of :title validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_associated :content def project_id wiki.project.id end def visible?(user=User.current) !user.nil? && user.allowed_to?(:view_wiki_pages, project) end def title=(value) value = Wiki.titleize(value) @previous_title = read_attribute(:title) if @previous_title.blank? write_attribute(:title, value) end def before_save self.title = Wiki.titleize(title) # Manage redirects if the title has changed if !@previous_title.blank? && (@previous_title != title) && !new_record? # Update redirects that point to the old title wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r| r.redirects_to = title r.title == r.redirects_to ? r.destroy : r.save end # Remove redirects for the new title wiki.redirects.find_all_by_title(title).each(&:destroy) # Create a redirect to the new title wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0" @previous_title = nil end end def before_destroy # Remove redirects to this page wiki.redirects.find_all_by_redirects_to(title).each(&:destroy) end def pretty_title WikiPage.pretty_title(title) end def content_for_version(version=nil) result = content.versions.find_by_version(version.to_i) if version result ||= content result end def diff(version_to=nil, version_from=nil) version_to = version_to ? version_to.to_i : self.content.version version_from = version_from ? version_from.to_i : version_to - 1 version_to, version_from = version_from, version_to unless version_from < version_to content_to = content.versions.find_by_version(version_to) content_from = content.versions.find_by_version(version_from) (content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil end def annotate(version=nil) version = version ? version.to_i : self.content.version c = content.versions.find_by_version(version) c ? WikiAnnotate.new(c) : nil end def self.pretty_title(str) (str && str.is_a?(String)) ? str.tr('_', ' ') : str end def project wiki.project end def text content.text if content end # Returns true if usr is allowed to edit the page, otherwise false def editable_by?(usr) !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) end def attachments_deletable?(usr=User.current) editable_by?(usr) && super(usr) end def parent_title @parent_title || (self.parent && self.parent.pretty_title) end def parent_title=(t) @parent_title = t parent_page = t.blank? ? nil : self.wiki.find_page(t) self.parent = parent_page end protected def validate errors.add(:parent_title, :invalid) if !@parent_title.blank? && parent.nil? errors.add(:parent_title, :circular_dependency) if parent && (parent == self || parent.ancestors.include?(self)) errors.add(:parent_title, :not_same_project) if parent && (parent.wiki_id != wiki_id) end end class WikiDiff attr_reader :diff, :words, :content_to, :content_from def initialize(content_to, content_from) @content_to = content_to @content_from = content_from @words = content_to.text.split(/(\s+)/) @words = @words.select {|word| word != ' '} words_from = content_from.text.split(/(\s+)/) words_from = words_from.select {|word| word != ' '} @diff = words_from.diff @words end end class WikiAnnotate attr_reader :lines, :content def initialize(content) @content = content current = content current_lines = current.text.split(/\r?\n/) @lines = current_lines.collect {|t| [nil, nil, t]} positions = [] current_lines.size.times {|i| positions << i} while (current.previous) d = current.previous.text.split(/\r?\n/).diff(current.text.split(/\r?\n/)).diffs.flatten d.each_slice(3) do |s| sign, line = s[0], s[1] if sign == '+' && positions[line] && positions[line] != -1 if @lines[positions[line]][0].nil? @lines[positions[line]][0] = current.version @lines[positions[line]][1] = current.author end end end d.each_slice(3) do |s| sign, line = s[0], s[1] if sign == '-' positions.insert(line, -1) else positions[line] = nil end end positions.compact! # Stop if every line is annotated break unless @lines.detect { |line| line[0].nil? } current = current.previous end @lines.each { |line| line[0] ||= current.version } end end ================================================ FILE: app/models/wiki_redirect.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class WikiRedirect < ActiveRecord::Base belongs_to :wiki validates_presence_of :title, :redirects_to validates_length_of :title, :redirects_to, :maximum => 255 end ================================================ FILE: app/models/workflow.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Workflow < ActiveRecord::Base belongs_to :role belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id' belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id' validates_presence_of :role, :old_status, :new_status # Returns workflow transitions count by tracker and role def self.count_by_tracker_and_role counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{Workflow.table_name} GROUP BY role_id, tracker_id") roles = Role.find(:all, :order => 'builtin, position') trackers = Tracker.find(:all, :order => 'position') result = [] trackers.each do |tracker| t = [] roles.each do |role| row = counts.detect {|c| c['role_id'] == role.id.to_s && c['tracker_id'] == tracker.id.to_s} t << [role, (row.nil? ? 0 : row['c'].to_i)] end result << [tracker, t] end result end # Find potential statuses the user could be allowed to switch issues to def self.available_statuses(project, user=User.current) Workflow.find(:all, :include => :new_status, :conditions => {:role_id => user.roles_for_project(project).collect(&:id)}). collect(&:new_status). compact. uniq. sort end # Copies workflows from source to targets def self.copy(source_tracker, source_role, target_trackers, target_roles) unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) raise ArgumentError.new("source_tracker or source_role must be specified") end target_trackers = [target_trackers].flatten.compact target_roles = [target_roles].flatten.compact target_trackers = Tracker.all if target_trackers.empty? target_roles = Role.all if target_roles.empty? target_trackers.each do |target_tracker| target_roles.each do |target_role| copy_one(source_tracker || target_tracker, source_role || target_role, target_tracker, target_role) end end end # Copies a single set of workflows from source to target def self.copy_one(source_tracker, source_role, target_tracker, target_role) unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && source_role.is_a?(Role) && !source_role.new_record? && target_tracker.is_a?(Tracker) && !target_tracker.new_record? && target_role.is_a?(Role) && !target_role.new_record? raise ArgumentError.new("arguments can not be nil or unsaved objects") end if source_tracker == target_tracker && source_role == target_role false else transaction do delete_all :tracker_id => target_tracker.id, :role_id => target_role.id connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" + " FROM #{Workflow.table_name}" + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" end true end end end ================================================ FILE: app/views/account/login.html.erb ================================================
    <%= render :partial => '/home/header', :locals => {:page => 'login'} %>
    <%= render_flash_messages %> <%= javascript_tag "$('#username').focus();" %>
    ================================================ FILE: app/views/account/lost_password.html.erb ================================================

    <%=l(:label_password_lost)%>

    <% form_tag({:action=> "lost_password"}, :class => "tabular") do %>

    <%= text_field_tag 'mail', nil, :size => 40 %>

    <%= submit_tag l(:button_submit), :disable_with => l(:button_working) %>

    <% end %>
    ================================================ FILE: app/views/account/password_recovery.html.erb ================================================

    <%=l(:label_password_lost)%>

    <%= error_messages_for 'user' %> <% form_tag({:token => @token.value}) do %>

    <%= password_field_tag 'new_password', nil, :size => 25 %>
    <%= l(:text_caracters_minimum, 4) %>

    <%= password_field_tag 'new_password_confirmation', nil, :size => 25 %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/account/register.html.erb ================================================
    <%= render :partial => '/home/header', :locals => {:page => 'register'} %>
    <%= render_flash_messages %>
    <% end %> ================================================ FILE: app/views/activity_stream_preferences/index.html.erb ================================================ <% html_title('Configure Your Activity Stream Access') %>
    <% if flash.now[:success] %><%= flash.now[:success] %><% end %> <%= error_messages_for :activity_stream_preference %>

    Activity Feed Access Preferences

    Check = activities are printed.


    <% form_for(ActivityStreamPreference.new) do |f| %> <%= hidden_field_tag "#{ACTIVITY_STREAM_USER_MODEL_ID}", @user_id %> <% ACTIVITY_STREAM_LOCATIONS.each_value do |location| %> <% end %> <% ACTIVITY_STREAM_LOCATIONS.each_key do |location| %> <% end %> <% ACTIVITY_STREAM_ACTIVITIES.each do |sym,activity| %> <% ACTIVITY_STREAM_LOCATIONS.each_key do |location| %> <% key = "#{sym}.#{location}" %> <% end %> <% end %>
    Activity<%=h location %>
    [Toggle All]<%= check_box_tag "all", location, false, {:onclick => "all_clicked(this, '#{location}')"} %>

    <%=h activity %><%= check_box_tag "locations[]", key, @activities[key].nil?, {:id => location} %>




    <%= f.submit 'Save My Activity Stream Access Preferences' %> <% end %>


    ================================================ FILE: app/views/activity_streams/_activity_comment.html.erb ================================================ <%= avatar(User.current, :size => "24") %> ================================================ FILE: app/views/activity_streams/_activity_stream.html.erb ================================================

    <%= link_to as.actor_name, {:controller => :users, :action => :show, :id => as.actor_id} %> <%= as.verb.tr('_', ' ') %> <%= link_to_activity(as) %> <%= action_times(as.count) %> <% if @project.nil? || @project.id != as.project_id %> in <%=link_to h(as.project_name), :controller => 'projects', :action => 'show', :id => as.project_id %><% end %> <%= refresh_link %>

    <%= avatar(as.actor_email, :size => '44') %>
    <%= link_to title_for_activity_stream(as) , url_for_activity_stream(as), :class => class_for_activity_stream(as) %>
    <%= format_activity_description(as.object_description) %>
      <%= since_tag(as.created_at) %> ago in <%= link_to h(as.project_name), :controller => 'projects', :action => 'show', :id => as.project_id %> <%= link_to " - #{as.tracker_name} ##{as.object_id}", url_for_activity_stream(as), {:class => class_for_activity_stream(as)} if as.tracker_name%>
    ================================================ FILE: app/views/activity_streams/_activity_stream_feed.atom.builder ================================================ this_url = "http://#{request.host_with_port}/feeds/your_activities/#{@user.activity_stream_token}" atom_feed(:url => this_url) do |feed| feed.title("#{ACTIVITY_STREAM_SERVICE_STRING}: Activity Stream for #{@user.send(ACTIVITY_STREAM_USER_MODEL_NAME)}") feed.updated(@activity_streams.first ? @activity_streams.first.created_at : Time.now.utc) for activity_stream in @activity_streams feed.entry(activity_stream.object ? activity_stream.object : activity_stream) do |entry| entry.title(activity_stream.activity.humanize) entry.content(render(:partial => 'activity_streams/activity_stream.html.erb', :locals => {:activity_stream => activity_stream}), :type => 'html') entry.author do |author| author.name(activity_stream.actor ? activity_stream.actor.send(activity_stream.actor_name_method) : '(deleted)' ) end end end end ================================================ FILE: app/views/activity_streams/_activity_stream_group.erb ================================================ <%= render :partial => 'activity_streams/activity_stream', :locals => {:as => asg[0], :refresh_link => refresh_link} %> <% for as in asg.to_a.reverse %> <%= render :partial => 'activity_streams/activity_stream_sub', :locals => {:as => as} %> <% end unless asg.length == 1 && !asg[0].indirect_object_id %>
    <% if User.current.logged? and (asg[0].object_type.downcase == "issue" || asg[0].object_type.downcase == "message") %> <%= render :partial => 'activity_streams/activity_comment', :locals => {:as => asg[0]} %>
    <% end %> ================================================ FILE: app/views/activity_streams/_activity_stream_list.html.erb ================================================ <% activities_by_item = ActivityStream.fetch(user_id,project_id, with_subprojects, limit, max_created_at) %> <%link = link_to_remote "refresh", { :url => {:controller => :activity_streams, :action => :index, :user_id => user_id, :project_id => project_id, :with_subprojects => with_subprojects, :limit => limit, :max_created_at => nil, :refresh => true }, :method => :get, :class => "right" } unless (defined? show_refresh) && !show_refresh %> <% activities_by_item.each_with_index do |asg, count| %> <%= render :partial => 'activity_streams/activity_stream_group', :locals => {:asg => asg[1], :refresh_link => (count == 0 ? link : nil)} %> <% end %>
    <% unless activities_by_item.empty? %>
    <%= link_to_remote "Show more ...", { :url => {:controller => :activity_streams, :action => :index, :user_id => user_id, :project_id => project_id, :with_subprojects => with_subprojects, :limit => limit, :max_created_at => activities_by_item.last[1].last.created_at }, :method => :get } unless activities_by_item.last.nil? %>
    <% end %> <%= content_tag('p', l(:label_no_data), :class => 'nodata') if activities_by_item.empty? %>
    ================================================ FILE: app/views/activity_streams/_activity_stream_sub.html.erb ================================================ <%= avatar(as.actor_email, :size => "24") %> <%= link_to as.actor_name, self.send("#{as.actor_type.downcase}_path", as.actor_id) %> <%= as.verb.tr('_', ' ') %> this<%= action_times(as.count) %> <%= since_tag(as.created_at) %> ago <% if as.indirect_object_id -%>

    <%= render :partial => 'activity_streams/indirect_object', :locals => {:as => as} %>

    <% end %> ================================================ FILE: app/views/activity_streams/_indirect_object.html.erb ================================================ <% if as.indirect_object_type.to_s == "Journal" && as.indirect_object_phrase == "GENERATEDETAILS" %> <%= render_journal_details(as.indirect_object)%> <% else %> <%= as.indirect_object_phrase %> <%= format_activity_description(as.indirect_object_description) %> <% end %> ================================================ FILE: app/views/activity_streams/_table_header.html.erb ================================================ Date Activity ================================================ FILE: app/views/activity_streams/edit.html.erb ================================================

    Editing activity_stream

    <% form_for(@activity_stream) do |f| %> <%= f.error_messages %>

    <%= f.label :activity %>
    <%= f.text_field :activity %>

    <%= f.label :actor_id %>
    <%= f.text_field :actor_id %>

    <%= f.label :actor_type %>
    <%= f.text_field :actor_type %>

    <%= f.label :count %>
    <%= f.text_field :count %>

    <%= f.label :object_id %>
    <%= f.text_field :object_id %>

    <%= f.label :object_type %>
    <%= f.text_field :object_type %>

    <%= f.label :status %>
    <%= f.text_field :status %>

    <%= f.submit "Update" %>

    <% end %> <%= link_to 'Show', @activity_stream %> | <%= link_to 'Back', activity_streams_path %> ================================================ FILE: app/views/activity_streams/index.html.erb ================================================ <% @page_header_name = l(:label_browse_activity) %>

    All Site ActivityStreams

    <% @grouped_by_item.each do |key, value| %> <%= render :partial => 'activity_streams/activity_stream_group', :locals => {:activity_stream_group_name => key, :activity_stream_group => value} %> <% end %>
    ================================================ FILE: app/views/activity_streams/new.html.erb ================================================

    New activity_stream

    <% form_for(@activity_stream) do |f| %> <%= f.error_messages %>

    <%= f.label :activity %>
    <%= f.text_field :activity %>

    <%= f.label :actor_id %>
    <%= f.text_field :actor_id %>

    <%= f.label :actor_type %>
    <%= f.text_field :actor_type %>

    <%= f.label :count %>
    <%= f.text_field :count %>

    <%= f.label :object_id %>
    <%= f.text_field :object_id %>

    <%= f.label :object_type %>
    <%= f.text_field :object_type %>

    <%= f.label :status %>
    <%= f.text_field :status %>

    <%= f.submit "Create" %>

    <% end %> <%= link_to 'Back', activity_streams_path %> ================================================ FILE: app/views/activity_streams/show.html.erb ================================================

    Activity: <%=h @activity_stream.activity %>

    Actor: <%=h @activity_stream.actor_id %>

    Actor type: <%=h @activity_stream.actor_type %>

    Count: <%=h @activity_stream.count %>

    Object: <%=h @activity_stream.object_id %>

    Object type: <%=h @activity_stream.object_type %>

    Status: <%=h @activity_stream.status %>

    <%= link_to 'Edit', edit_activity_stream_path(@activity_stream) %> | <%= link_to 'Back', activity_streams_path %> ================================================ FILE: app/views/admin/_menu.html.erb ================================================
    • <%= link_to l(:label_project_plural), {:controller => 'admin', :action => 'projects'}, :class => 'projects' %>
    • <%= link_to l(:label_user_plural), {:controller => 'users'}, :class => 'users' %>
    • <%= link_to l(:label_role_and_permissions), {:controller => 'roles'}, :class => 'roles' %>
    • <%= link_to l(:label_tracker_plural), {:controller => 'trackers'}, :class => 'trackers' %>
    • <%= link_to l(:label_issue_status_plural), {:controller => 'issue_statuses'}, :class => 'issue_statuses' %>
    • <%= link_to l(:label_workflow), {:controller => 'workflows', :action => 'edit'}, :class => 'workflows' %>
    • <%= link_to l(:label_enumerations), {:controller => 'enumerations'}, :class => 'enumerations' %>
    • <%= link_to l(:label_settings), {:controller => 'settings'}, :class => 'settings' %>
    • <% menu_items_for(:admin_menu) do |item| -%>
    • <%= link_to h(item.caption), item.url, item.html_options %>
    • <% end -%>
    • <%= link_to l(:label_plugins), {:controller => 'admin', :action => 'plugins'}, :class => 'plugins' %>
    • <%= link_to l(:label_information_plural), {:controller => 'admin', :action => 'info'}, :class => 'info' %>
    • <%= link_to :Stats, {:controller => 'admin', :action => 'user_stats'}, :class => 'info' %>
    • <%= link_to :User_Data_Dump, {:controller => 'admin', :action => 'user_data_dump', :format => :csv}, :class => 'settings' %>
    ================================================ FILE: app/views/admin/_no_data.html.erb ================================================
    <% form_tag({:action => 'default_configuration'}) do %> <%= simple_format(l(:text_no_configuration_data)) %>

    <%= l(:field_language) %>: <%= select_tag 'lang', options_for_select(lang_options_for_select(false), current_language.to_s) %>

    <%= submit_tag l(:text_load_default_configuration), :disable_with => l(:button_working) %>

    <% end %>
    ================================================ FILE: app/views/admin/index.html.erb ================================================

    <%=l(:label_administration)%>

    <%= render :partial => 'no_data' if @no_configuration_data %> <%= render :partial => 'menu' %>
    <% html_title(l(:label_administration)) -%> ================================================ FILE: app/views/admin/info.html.erb ================================================

    <%=l(:label_information_plural)%>

    <%= Redmine::Info.versioned_name %> (<%= @db_adapter_name %>)

    <% @checklist.each do |label, result| %> <% end %>
    <%= l(label) %> <%= image_tag((result ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;") %>
    <% html_title(l(:label_information_plural)) -%> ================================================ FILE: app/views/admin/plugins.html.erb ================================================

    <%= l(:label_plugins) %>

    <% if @plugins.any? %> <% @plugins.each do |plugin| %> <% end %>
    <%=h plugin.name %> <%= content_tag('span', h(plugin.description), :class => 'description') unless plugin.description.blank? %> <%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %> <%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %> <%=h plugin.version %> <%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %> ================================================ FILE: app/views/admin/projects.html.erb ================================================
    <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>

    <%=l(:label_project_plural)%>

    <% form_tag({}, :method => :get) do %>
    <%= l(:label_filter_plural) %> <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> <%= text_field_tag 'name', params[:name], :size => 30 %>
    <%= submit_tag l(:button_apply), :class => "small", :name => nil, :disable_with => l(:button_working) %>
    <% end %>   <% for project in @projects %> <%= css_project_classes(project) %>"> <% end %>
    <%=l(:label_project)%> <%=l(:field_description)%> <%=l(:field_is_public)%> <%=l(:field_created_at)%>
    <%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %> <%= textilizable project.short_description, :project => project %> <%= image_tag 'true.png' if project.is_public? %> <%= format_date(project.created_at) %> <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %> <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if project.archived? && (project.parent.nil? || project.parent.active?) %> <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %> <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
    <% html_title(l(:label_project_plural)) -%> ================================================ FILE: app/views/admin/user_stats.html.erb ================================================ <%= javascript_include_tag 'raphael.min', 'g.raphael.min', 'g.line.min', 'g.bar.min' %>

    Signups

    Daily signups for the last 14 days
    <%= raphael_report_tag(User.daily_registrations_report,{:width => 600, :height => 150},{ :shade => false }) %>
    Weekly signups <%= raphael_report_tag(User.weekly_registrations_report,{:width => 600, :height => 150},{}) %>

    Logins

    Daily logins for the last 14 days
    <%= raphael_report_tag(Track.daily_logins_report,{:width => 600, :height => 150},{ :shade => false }) %>
    Weekly logins <%= raphael_report_tag(Track.weekly_logins_report,{:width => 600, :height => 150},{}) %>

    New Projects

    Daily new projects for the last 14 days
    <%= raphael_report_tag(Project.daily_new_projects_report,{:width => 600, :height => 150},{ :shade => false }) %>
    Weekly new projects <%= raphael_report_tag(Project.weekly_new_projects_report,{:width => 600, :height => 150},{"smooth" => true}) %>
    ================================================ FILE: app/views/attachments/_form.html.erb ================================================ ================================================ FILE: app/views/attachments/_links.html.erb ================================================
    <% for attachment in attachments %>

    <%= link_to_attachment attachment, :class => 'icon icon-attachment' -%> <%= h(" - #{attachment.description}") unless attachment.description.blank? %> (<%= number_to_human_size attachment.filesize %>) <% if options[:deletable] %> <%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => attachment}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'delete', :title => l(:button_delete) %> <% end %> <% if options[:author] %> <%= attachment.author %>, <%= format_time(attachment.created_at) %> <% end %>

    <% end %>
    ================================================ FILE: app/views/attachments/_table.erb ================================================ <% for attachment in attachments %> <% end %>
    <%= link_to_attachment attachment, :class => 'icon icon-attachment' -%> <%= h(" - #{attachment.description}") unless attachment.description.blank? %> (<%= number_to_human_size attachment.filesize %>) <% if options[:deletable] %> <%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => attachment}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'delete', :title => l(:button_delete) %> <% end %> <% if options[:author] %> <%= attachment.author %>, <%= format_time(attachment.created_at) %> <% end %>
    ================================================ FILE: app/views/attachments/diff.html.erb ================================================

    <%=h @attachment.filename %>

    <%= h("#{@attachment.description} - ") unless @attachment.description.blank? %> <%= @attachment.author %>, <%= format_time(@attachment.created_at) %>

    <%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%> (<%= number_to_human_size @attachment.filesize %>)

      <%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %> <% html_title @attachment.filename %> <% content_for :header_tags do -%> <%= stylesheet_link_tag "scm" -%> <% end -%> ================================================ FILE: app/views/attachments/file.html.erb ================================================

    <%=h @attachment.filename %>

    <%= h("#{@attachment.description} - ") unless @attachment.description.blank? %> <%= @attachment.author %>, <%= format_time(@attachment.created_at) %>

    <%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%> (<%= number_to_human_size @attachment.filesize %>)

      <%= render :partial => 'common/file', :locals => {:content => @content, :filename => @attachment.filename} %> <% html_title @attachment.filename %> <% content_for :header_tags do -%> <%= stylesheet_link_tag "scm" -%> <% end -%> ================================================ FILE: app/views/auth_sources/_form.html.erb ================================================ <%= error_messages_for 'auth_source' %>

    <%= text_field 'auth_source', 'name' %>

    <%= text_field 'auth_source', 'host' %>

    <%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

    <%= text_field 'auth_source', 'account' %>

    <%= password_field 'auth_source', 'account_password', :name => 'ignore', :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), :onfocus => "this.value=''; this.name='auth_source[account_password]';", :onchange => "this.name='auth_source[account_password]';" %>

    <%= text_field 'auth_source', 'base_dn', :size => 60 %>

    <%= check_box 'auth_source', 'onthefly_register' %>

    <%=l(:label_attribute_plural)%>

    <%= text_field 'auth_source', 'attr_login', :size => 20 %>

    <%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

    <%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

    <%= text_field 'auth_source', 'attr_mail', :size => 20 %>

    ================================================ FILE: app/views/auth_sources/edit.html.erb ================================================

    <%=l(:label_auth_source)%> (<%= @auth_source.auth_method_name %>)

    <% form_tag({:action => 'update', :id => @auth_source}, :class => "tabular") do %> <%= render :partial => 'form' %>
    <%= submit_tag l(:button_save) %>
    <% end %> ================================================ FILE: app/views/auth_sources/list.html.erb ================================================
    <%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %>

    <%=l(:label_auth_source_plural)%>

    <% for source in @auth_sources %> "> <% end %>
    <%=l(:field_name)%> <%=l(:field_type)%> <%=l(:field_host)%> <%=l(:label_user_plural)%>
    <%= link_to source.name, :action => 'edit', :id => source%> <%= source.auth_method_name %> <%= source.host %> <%= source.users.count %> <%= link_to l(:button_test), :action => 'test_connection', :id => source %> <%= button_to l(:button_delete), { :action => 'destroy', :id => source }, :confirm => l(:text_are_you_sure), :class => "button-small", :disabled => source.users.any? %>

    <%= pagination_links_full @auth_source_pages %>

    ================================================ FILE: app/views/auth_sources/new.html.erb ================================================

    <%=l(:label_auth_source_new)%> (<%= @auth_source.auth_method_name %>)

    <% form_tag({:action => 'create'}, :class => "tabular") do %> <%= render :partial => 'form' %>
    <%= submit_tag l(:button_create) %>
    <% end %> ================================================ FILE: app/views/boards/_form.html.erb ================================================ <%= error_messages_for 'board' %>

    <%= f.text_field :name, :required => true %>

    <%= f.text_field :description, :required => true, :size => 80 %>

    ================================================ FILE: app/views/boards/edit.html.erb ================================================

    <%= l(:label_board) %>

    <% labelled_tabular_form_for :board, @board, :url => {:action => 'edit', :id => @board} do |f| %> <%= render :partial => 'form', :locals => {:f => f} %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/boards/index.html.erb ================================================ <%= help_section "project_discussion" %>

    <%= l(:label_board_plural) %>

    <% for board in @boards %> <% end %>
    <%= l(:label_board) %> <%= l(:label_topic_plural) %> <%= l(:label_message_plural) %> <%= l(:label_message_last) %>
    <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "board" %>
    <%=h board.description %>
    <%= board.topics_count %> <%= board.messages_count %> <% if board.last_message %> <%= authoring board.last_message.created_at, board.last_message.author %>
    <%= link_to_message board.last_message %> <% end %>
    <% html_title l(:label_board_plural) %> ================================================ FILE: app/views/boards/new.html.erb ================================================

    <%= l(:label_board_new) %>

    <% labelled_tabular_form_for :board, @board, :url => {:action => 'new'} do |f| %> <%= render :partial => 'form', :locals => {:f => f} %>
    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/boards/show.html.erb ================================================ <%= help_section "project_discussion" %> <%= breadcrumb link_to(l(:label_board_plural), {:controller => 'boards', :action => 'index', :project_id => @project}) %>

    <%=h @board.name %>

    <%=h @board.description %>

    <% if @topics.any? %>
    <%= sort_header_tag('created_at', :caption => l(:field_created_at)) %> <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %> <%= sort_header_tag('updated_at', :caption => l(:label_message_last)) %> <% @topics.each do |topic| %> <% end %>
    <%= l(:field_subject) %> <%= l(:field_author) %>
    <%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %> <%= topic.author %> <%= format_time(topic.created_at) %> <%= topic.replies_count %> <% if topic.last_reply %> <%= authoring topic.last_reply.created_at, topic.last_reply.author %>
    <%= link_to_message topic.last_reply %> <% end %>

    <%= pagination_links_full @topic_pages, @topic_count %>

    <% else %>

    <%= l(:label_no_data) %>

    <% end %> <% html_title h(@board.name) %> <% content_for :actionmenu do %>
    • <%= link_to_if_authorized l(:label_topic_new), {:controller => 'messages', :action => 'new', :board_id => @board}, :class => 'icon icon-add', :onclick => '$("#add-message").show(); $("#message_subject").focus(); return false;' %>
    • <%= watcher_tag(@board, User.current) %>
    <% end %> ================================================ FILE: app/views/common/403.html.erb ================================================

    403

    <%= l(:notice_not_authorized) %>

    Back

    <% html_title '403' %> ================================================ FILE: app/views/common/404.html.erb ================================================

    404

    <%= l(:notice_file_not_found) %>

    Back

    <% html_title '404' %> ================================================ FILE: app/views/common/_calendar.html.erb ================================================ <% 7.times do |i| %><% end %> <% day = calendar.startdt while day <= calendar.enddt %> <%= "" if day.cwday == calendar.first_wday %> <%= '' if day.cwday==calendar.last_wday and day!=calendar.enddt %> <% day = day + 1 end %>
    <%= day_name( (calendar.first_wday+i)%7 ) %>
    #{day.cweek}

    <%= day.day %>

    <% calendar.events_on(day).each do |i| %> <% if i.is_a? Issue %>
    <%= if day == i.start_date && day == i.due_date image_tag('arrow_bw.png') elsif day == i.start_date image_tag('arrow_from.png') elsif day == i.due_date image_tag('arrow_to.png') end %> <%= h("#{i.project} -") unless @project && @project == i.project %> <%= link_to_issue i, :truncate => 30 %> <%= render_issue_tooltip i %>
    <% else %> <%= h("#{i.project} -") unless @project && @project == i.project %> <% end %> <% end %>
    ================================================ FILE: app/views/common/_diff.html.erb ================================================ <% diff = Redmine::UnifiedDiff.new(diff, :type => diff_type, :max_lines => Setting.diff_max_lines_displayed.to_i) -%> <% diff.each do |table_file| -%>
    <% if diff_type == 'sbs' -%> <% prev_line_left, prev_line_right = nil, nil -%> <% table_file.keys.sort.each do |key| -%> <% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%> <% end -%> <% prev_line_left, prev_line_right = table_file[key].nb_line_left.to_i, table_file[key].nb_line_right.to_i -%> <% end -%>
    <%= table_file.file_name %>
    ......
    <%= table_file[key].nb_line_left %>
    <%=to_utf8 table_file[key].line_left %>
    <%= table_file[key].nb_line_right %>
    <%=to_utf8 table_file[key].line_right %>
    <% else -%> <% prev_line_left, prev_line_right = nil, nil -%> <% table_file.keys.sort.each do |key, line| %> <% if prev_line_left && prev_line_right && (table_file[key].nb_line_left != prev_line_left+1) && (table_file[key].nb_line_right != prev_line_right+1) -%> <% end -%> <% if table_file[key].line_left.empty? -%> <% else -%> <% end -%> <% prev_line_left = table_file[key].nb_line_left.to_i if table_file[key].nb_line_left.to_i > 0 -%> <% prev_line_right = table_file[key].nb_line_right.to_i if table_file[key].nb_line_right.to_i > 0 -%> <% end -%>
    <%= table_file.file_name %>
    ......
    <%= table_file[key].nb_line_left %> <%= table_file[key].nb_line_right %>
    <%=to_utf8 table_file[key].line_right %>
    <%=to_utf8 table_file[key].line_left %>
    <% end -%>
    <% end -%> <%= l(:text_diff_truncated) if diff.truncated? %> ================================================ FILE: app/views/common/_file.html.erb ================================================
    <% line_num = 1 %> <% syntax_highlight(filename, to_utf8(content)).each_line do |line| %> <% line_num += 1 %> <% end %>
    <%= line_num %>
    <%= line %>
    ================================================ FILE: app/views/common/_main_menu_home.html.erb ================================================
    • <%= link_to l(:label_my_page), {:controller => "welcome", :action => "index"}, :class => (active_page == :mypage ? 'gt-active' : '') %>
    • <%= link_to l(:label_my_issues), {:controller => "my", :action => "issues"}, :class => (active_page == :myissues ? 'gt-active' : '') %>
    • <%= link_to l(:label_my_projects), {:controller => "my", :action => "projects"}, :class => (active_page == :myprojects ? 'gt-active' : '') %>
    • <%= link_to l(:label_my_account), {:controller => "my", :action => "account"}, :class => (active_page == :myaccount ? 'gt-active' : '') %>
    • <%= link_to l(:label_notifications), {:controller => "notifications", :action => "index"}, :class => (active_page == :notifications ? 'gt-active' : '') %>
    ================================================ FILE: app/views/common/_preview.html.erb ================================================
    <%= l(:label_preview) %> <%= textilizable @text, :attachments => @attachements, :object => @previewed %>
    ================================================ FILE: app/views/common/_tabs.html.erb ================================================ <% selected_tab = params[:tab] ? params[:tab].to_s : tabs.first[:name] %>
      <% tabs.each do |tab| -%> <% tab[:label_ending] ||="" %>
    • <%= link_to (l(tab[:label])+ tab[:label_ending]), { :tab => tab[:name] }, :id => "tab-#{tab[:name]}", :class => "tab-top #{(tab[:name] != selected_tab ? nil : 'selected')}", :onclick => "showTab('#{tab[:name]}'); this.blur(); return false;" %>
    • <% end -%>
    <% tabs.each do |tab| -%> <%= content_tag('div', render(:partial => tab[:partial], :locals => tab[:locals] ), :id => "tab-content-#{tab[:name]}", :class => "tab-content #{(tab[:name] != selected_tab ? 'hidden' : nil)}") %> <% end -%> ================================================ FILE: app/views/common/feed.atom.rxml ================================================ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do xml.title truncate_single_line(@title, :length => 100) xml.link "rel" => "self", "href" => url_for(params.merge(:only_path => false)) xml.link "rel" => "alternate", "href" => url_for(params.merge(:only_path => false, :format => nil, :key => nil)) xml.id url_for(:controller => 'welcome', :only_path => false) xml.updated((@items.first ? @items.first.event_datetime : Time.now).xmlschema) xml.author { xml.name "#{Setting.app_title}" } xml.generator(:uri => Redmine::Info.url) { xml.text! Redmine::Info.app_name; } @items.each do |item| xml.entry do url = url_for(item.event_url(:only_path => false)) if @project xml.title truncate_single_line(item.event_title, :length => 100) else xml.title truncate_single_line("#{item.project} - #{item.event_title}", :length => 100) end xml.link "rel" => "alternate", "href" => url xml.id url xml.updated item.event_datetime.xmlschema author = item.event_author if item.respond_to?(:event_author) xml.author do xml.name(author) xml.email(author.mail) if author.is_a?(User) && !author.mail.blank? && !author.pref.hide_mail end if author xml.content "type" => "html" do xml.text! textilizable(item, :event_description, :only_path => false) end end end end ================================================ FILE: app/views/credit_distributions/edit.html.erb ================================================

    Editing credit_distribution

    <% form_for(@credit_distribution) do |f| %> <%= f.error_messages %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :retro_id %>
    <%= f.text_field :retro_id %>

    <%= f.label :amount %>
    <%= f.text_field :amount %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @credit_distribution %> | <%= link_to 'Back', credit_distributions_path %> ================================================ FILE: app/views/credit_distributions/index.html.erb ================================================

    Listing credit_distributions

    <% @credit_distributions.each do |credit_distribution| %> <% end %>
    User Workstream translation missing: en, field_retro translation missing: en, field_amount
    <%=h credit_distribution.user_id %> <%=h credit_distribution.project_id %> <%=h credit_distribution.retro_id %> <%=h credit_distribution.amount %> <%= link_to 'Show', credit_distribution %> <%= link_to 'Edit', edit_credit_distribution_path(credit_distribution) %> <%= link_to 'Destroy', credit_distribution, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New credit_distribution', new_credit_distribution_path %> ================================================ FILE: app/views/credit_distributions/new.html.erb ================================================

    New credit_distribution

    <% form_for(@credit_distribution) do |f| %> <%= f.error_messages %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :retro_id %>
    <%= f.text_field :retro_id %>

    <%= f.label :amount %>
    <%= f.text_field :amount %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', credit_distributions_path %> ================================================ FILE: app/views/credit_distributions/show.html.erb ================================================

    User: <%=h @credit_distribution.user_id %>

    Workstream: <%=h @credit_distribution.project_id %>

    translation missing: en, field_retro: <%=h @credit_distribution.retro_id %>

    translation missing: en, field_amount: <%=h @credit_distribution.amount %>

    <%= link_to 'Edit', edit_credit_distribution_path(@credit_distribution) %> | <%= link_to 'Back', credit_distributions_path %> ================================================ FILE: app/views/credit_transfers/_credit_transfer_history.html.erb ================================================
    <% if credit_transfers.length == 0 %>

    <%= l(:label_no_data) %>

    <% else %> <% credit_transfers.sort { |b,a| a.created_at <=> b.created_at } credit_transfers.each do |ct| %> <% end %>
    Date Amount Sender Recipient Workstream
    <%= format_date ct.created_at %> <%=h number_to_currency ct.amount.round.to_i, :unit => "", :separator => ".", :delimiter => ",", :precision => 0 %> <%= link_to_user_or_you ct.sender %> <%= link_to_user_or_you ct.recipient %> <%= link_to_project ct.project %>
    <% end %>
    ================================================ FILE: app/views/credit_transfers/_eligible_recipients.erb ================================================ Available credits: <%= @total_credits %>

    <% if @user_list.length > 0 %>

    <%= collection_select(:credit_transfer, :recipient_id, @user_list, :user_id, :name, :prompt => l(:label_select)) %>

    <%= text_field_tag 'amount', nil, :size => 10, :style => "width:40%" %>

    <%= text_area('note', nil, :cols => 30, :rows => 6) %>

    <%= submit_tag l(:button_submit), :disable_with => l(:button_working) %>

    <% else %> There are no eligible recipients for credits from this project workstream.
    Learn more about how membership rules work
    <% end %> ================================================ FILE: app/views/credit_transfers/_new_credit_transfer.html.erb ================================================

    <%=l(:label_new_credit_transfer) %>

    <% form_tag({:action => :create}) do %> <% if project_list %> <%= collection_select(:credit_transfer, :project_id, project_list, :project_id, :name,{:prompt => l(:label_select), :selected => @selected_project_id.to_i}) %> <% else %>
    <%= content_tag 'p', l(:label_no_credits), :class => "nodata" %>
    <% end %>
    <% if @selected_project_id %> <%= render :partial => "eligible_recipients" %> <% end %>
    <%= observe_field :credit_transfer_project_id, :url => {:controller => :credit_transfers, :action => :eligible_recipients, :only_path => :false }, :update => :users_container, :with => 'project_id' %> <% end %> ================================================ FILE: app/views/credit_transfers/index.html.erb ================================================

    <%= l(:label_credit_transfers) %>

    <%= render :partial => 'new_credit_transfer', :locals => {:project_list => @project_list} %>

    <%=l(:label_previous_credit_transfers) %>

    <%= render :partial => 'credit_transfer_history', :locals => {:credit_transfers => @credit_transfers} %>
    ================================================ FILE: app/views/credits/_credit_breakdown.html.erb ================================================
    <% pie_data = [] pie_labels = [] total_points = 0 unit = unit_for(@project) unit = '' if unit != '$' group_credits.each_value {|credits| total_points += credits.collect(&:amount).sum } group_credits.keys.sort.each do |owner_id| total_amount = group_credits[owner_id].collect(&:amount).sum.round.to_i percentage_points = total_points == 0 ? 0 : (total_amount / total_points * 100).round_to(1).to_i pie_data << percentage_points.round.to_i pie_labels << User.find(owner_id).firstname + " #{number_to_currency total_amount, :unit => unit, :separator => ".", :delimiter => ",", :precision => 0}" end %> unit, :separator => ".", :delimiter => ",", :precision => 0}", :size => '400x200', :data => pie_data, :labels => pie_labels ) %>"/>
    ================================================ FILE: app/views/credits/_credit_history.html.erb ================================================

    <%=l(:label_credit_history) %>

    <% @credits_by_day = @credits.group_by{|credit| credit.issued_on.to_date} unit = unit_for(@project) if @credits_by_day.length == 0 %> <%= content_tag 'p', l(:label_no_data), :class => "nodata" %> <% else %> <% @credits_by_day.sort { |b,a| a[0] <=> b[0] } @credits_by_day.each do |cbd| %> <% cbd[1].each do |credit|%> <% if authorize_for(:credits, :edit) %> <% end %> <% end %> <% end %>
    <%= cbd[0].to_s %>
    <%=h number_to_currency credit.amount.round.to_i, :unit => unit, :separator => ".", :delimiter => ",", :precision => 0 %> <%=link_to_user_from_id credit.owner_id %> <%=h credit.settled_on.nil? ? credit.enabled ? "Active" : "Inactive" : "Settled" %><%= link_to 'Edit', :controller => :credits, :action => :edit, :id => credit %> | <%= link_to 'Delete', :controller => :credits, :action => :destroy, :id => credit %>
    <% end %>
    ================================================ FILE: app/views/credits/_credit_queue.html.erb ================================================

    <%=l(:label_credit_queue) %>

    <% @credits_by_day = @credits.group_by{|credit| credit.issued_on.to_date} unit = unit_for(@project) if @credits_by_day.length == 0 %> <%= content_tag 'p', l(:label_no_data), :class => "nodata" %> <% else %> <% @credits_by_day.sort { |b,a| a[0] <=> b[0] } @credits_by_day.each do |cbd| %> <% cbd[1].each do |credit| next if !credit.enabled || !credit.settled_on.nil? %> <% end %> <% end %>
    <%= cbd[0].to_s %>
    <%=h number_to_currency credit.amount.round.to_i, :unit => unit, :separator => ".", :delimiter => ",", :precision => 0 %> <%=link_to_user_from_id credit.owner_id %>
    <% end %>
    ================================================ FILE: app/views/credits/_my_credits.html.erb ================================================

    <%=l(:label_my_credits) %>

    <% @credits_by_day = @credits.group_by{|credit| credit.issued_on.to_date} unit = unit_for(@project) display_no_data = true; if @credits_by_day.length == 0 display_no_data = true; else %> <% @credits_by_day.sort { |b,a| a[0] <=> b[0] } @credits_by_day.each do |cbd| %> <% @date = ""%> <% cbd[1].each do |credit| next if credit.owner_id != User.current.id display_no_data = false; %> <%= @date %> <% @date = "" %> <% end %> <% end %>
    #{cbd[0].to_s}
    <%=h number_to_currency credit.amount.round.to_i, :unit => unit, :separator => ".", :delimiter => ",", :precision => 0 %> <%=h credit.settled_on.nil? ? credit.enabled ? "Active" : "Inactive" : "Settled" %> <%= credit_activation_link(credit, @project.id, params[:with_subprojects]) %>
    <% end %> <% if display_no_data %>

    <%= l(:label_no_data) %>

    <% end %>
    ================================================ FILE: app/views/credits/edit.html.erb ================================================

    Editing credit

    <% form_for @credit, :url => {:action => 'update', :id => @credit} do |f| %> <%= f.error_messages %>

    <%= f.label :amount %>
    <%= f.text_field :amount %>

    <%= f.label :owner_id %>
    <%= f.text_field :owner_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :enabled %>
    <%= f.check_box :enabled %>

    <%= f.submit 'Update' %>

    <% end %> ================================================ FILE: app/views/credits/index.html.erb ================================================

    Listing credits

    <% @credits.each do |credit| %> <% end %>
    Amount Owner Workstream Enabled
    <%=h credit.amount %> <%=h credit.owner_id %> <%=h credit.project_id %> <%=h credit.enabled %> <%= link_to 'Show', credit %> <%= link_to 'Edit', edit_credit_path(credit) %> <%= link_to 'Destroy', credit, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New credit', new_credit_path %> ================================================ FILE: app/views/credits/new.html.erb ================================================

    New credit

    <% form_for @credit, :url => {:action => 'create'} do |f| %> <%= f.error_messages %>

    <%= f.label :amount %>
    <%= f.text_field :amount %>

    <%= f.label :owner_id %>
    <%= f.text_field :owner_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :enabled %>
    <%= f.check_box :enabled %>

    <%= f.label :created_at %>
    <%= f.text_field :created_at %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to "Back", :controller => "credits" %> ================================================ FILE: app/views/credits/show.html.erb ================================================

    <%=l(:label_team) %>

    <%= error_messages_for 'member' %>

    Amount: <%=h @credit.amount %>

    Owner: <%=h @credit.owner_id %>

    Workstream: <%=h @credit.project_id %>

    Enabled: <%=h @credit.enabled %>

    <%= link_to 'Edit', edit_credit_path(@credit) %> | <%= link_to 'Back', credits_path %>
    <% html_title(l(:label_team)) %> ================================================ FILE: app/views/documents/_document.html.erb ================================================

    <%= link_to h(document.title), :controller => 'documents', :action => 'show', :id => document %>
    <% unless document.description.blank? %><%=h(truncate(document.description, :length => 250)) %>
    <% end %> <%= format_time(document.updated_at) %>

    ================================================ FILE: app/views/documents/_form.html.erb ================================================ <%= error_messages_for 'document' %>

    <%= text_field 'document', 'title', :size => 60 %>

    <%= textile_editor 'document', 'description', :cols => 60, :rows => 15, :class => 'wiki-edit' %>

    <%= textile_editor_initialize(:framework => :jquery) %>
    ================================================ FILE: app/views/documents/edit.html.erb ================================================

    <%=l(:label_document)%>

    <% form_tag({:action => 'edit', :id => @document}, :class => "tabular") do %> <%= render :partial => 'form' %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/documents/index.html.erb ================================================ <% if @grouped.empty? %>

    <%= l(:label_no_data) %>

    <% end %> <% @grouped.keys.sort.each do |group| %>

    <%= group %>

    <%= render :partial => 'documents/document', :collection => @grouped[group] %>
    <% end %> <% html_title(l(:label_document_plural)) -%> <% content_for :actionmenu do %>
    • <%= link_to_if_authorized l(:label_document_new), {:controller => 'documents', :action => 'new', :project_id => @project}, :class => 'icon icon-add', :onclick => '$("#add-document").show(); $("#document_title").focus(); return false;' %>
    <% end %> <% content_for :sidebar do %> <% form_tag({}, :method => :get) do %> <% end %> <% end %> ================================================ FILE: app/views/documents/new.html.erb ================================================

    <%=l(:label_document_new)%>

    <% form_tag({:controller => 'documents', :action => 'new', :project_id => @project}, :class => "tabular", :multipart => true) do %> <%= render :partial => 'documents/form' %>

    <%= render :partial => 'attachments/form' %>

    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/documents/show.html.erb ================================================

    <%=h @document.title %>

    <%= format_date @document.created_at %>

    <%= textilizable @document.description, :attachments => @document.attachments %>

    <%= l(:label_attachment_plural) %>

    <%= link_to_attachments @document %> <% if authorize_for('documents', 'add_attachment') %>

    <%= link_to l(:label_attachment_new), {}, :onclick => "$('#add_attachment_form').show(); $('#' + this.id).hide(); $('body').scrollTo('#add_attachment_form'); return false;", :id => 'attach_files_link', :class => "gt-btn-gray-large" %>

    <% form_tag({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>

    <%= render :partial => 'attachments/form' %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <% end %> <% end %> <% html_title @document.title -%> <% content_for :sidebar do %>

     

    • <%= link_to_if_authorized l(:button_edit), {:controller => 'documents', :action => 'edit', :id => @document}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
    • <%= link_to_if_authorized l(:button_delete), {:controller => 'documents', :action => 'destroy', :id => @document}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
    <% end %> ================================================ FILE: app/views/email_updates/new.html.erb ================================================ <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :myaccount} %> <% end %>

    <%= l(:label_email_update) %>

    <% form_for(@email_update) do |f| %>
    <%= f.text_field :mail %>

    <%= submit_tag l(:button_submit), :disable_with => l(:button_working) %>

    <% end %> ================================================ FILE: app/views/enterprises/edit.html.erb ================================================

    Editing enterprise

    <% form_for(@enterprise) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @enterprise %> | <%= link_to 'Back', enterprises_path %> ================================================ FILE: app/views/enterprises/index.html.erb ================================================

    Listing enterprises

    <% @enterprises.each do |enterprise| %> <% end %>
    <%= link_to 'Show', enterprise %> <%= link_to 'Edit', edit_enterprise_path(enterprise) %> <%= link_to 'Destroy', enterprise, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New enterprise', new_enterprise_path %> ================================================ FILE: app/views/enterprises/new.html.erb ================================================

    New enterprise

    <% form_for(@enterprise) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', enterprises_path %> ================================================ FILE: app/views/enterprises/show.html.erb ================================================ <%= link_to 'Edit', edit_enterprise_path(@enterprise) %> | <%= link_to 'Back', enterprises_path %> ================================================ FILE: app/views/enumerations/_form.html.erb ================================================ <%= error_messages_for 'enumeration' %>
    <%= hidden_field 'enumeration', 'type' %>

    <%= text_field 'enumeration', 'name' %>

    <%= check_box 'enumeration', 'active' %>

    <%= check_box 'enumeration', 'is_default' %>

    ================================================ FILE: app/views/enumerations/destroy.html.erb ================================================

    <%= l(@enumeration.option_name) %>: <%=h @enumeration %>

    <% form_tag({}) do %>

    <%= l(:text_enumeration_destroy_question, @enumeration.objects_count) %>

    <%= l(:text_enumeration_category_reassign_to) %> <%= select_tag 'reassign_to_id', ("" + options_from_collection_for_select(@enumerations, 'id', 'name')) %>

    <%= submit_tag l(:button_apply), :disable_with => l(:button_working) %>

    <%= link_to l(:button_cancel), :controller => 'enumerations', :action => 'index' %>

    <% end %> ================================================ FILE: app/views/enumerations/edit.html.erb ================================================

    <%= link_to l(@enumeration.option_name), :controller => 'enumerations', :action => 'index' %> » <%=h @enumeration %>

    <% form_tag({:action => 'update', :id => @enumeration}, :class => "tabular") do %> <%= render :partial => 'form' %> <%= submit_tag l(:button_save), :disable_with => l(:button_working) %> <% end %> <% form_tag({:action => 'destroy', :id => @enumeration}) do %> <%= submit_tag l(:button_delete), :disable_with => l(:button_working) %> <% end %> ================================================ FILE: app/views/enumerations/list.html.erb ================================================

    <%=l(:label_enumerations)%>

    <% Enumeration.get_subclasses.each do |klass| %>

    <%= l(klass::OptionName) %>

    <% enumerations = klass.shared %> <% if enumerations.any? %> <% enumerations.each do |enumeration| %> <% end %>
    <%= l(:field_name) %> <%= l(:field_is_default) %> <%= l(:field_active) %>
    <%= link_to h(enumeration), :action => 'edit', :id => enumeration %> <%= image_tag('true.png') if enumeration.is_default? %> <%= image_tag('true.png') if enumeration.active? %> <%= reorder_links('enumeration', {:action => 'update', :id => enumeration}) %> <%= link_to l(:button_delete), { :action => 'destroy', :id => enumeration }, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %>
    <% reset_cycle %> <% end %>

    <%= link_to l(:label_enumeration_new), { :action => 'new', :type => klass.name } %>

    <% end %> <% html_title(l(:label_enumerations)) -%> ================================================ FILE: app/views/enumerations/new.html.erb ================================================

    <%= link_to l(@enumeration.option_name), :controller => 'enumerations', :action => 'index' %> » <%=l(:label_enumeration_new)%>

    <% form_tag({:action => 'create'}, :class => "tabular") do %> <%= render :partial => 'form' %> <%= submit_tag l(:button_create), :disable_with => l(:button_working) %> <% end %> ================================================ FILE: app/views/help/show.html.erb ================================================

    <%= l('help_' + @help_key + '_title') %>

    <%= l('help_' + @help_key + '_description') %>

    ================================================ FILE: app/views/help_sections/_show.html.erb ================================================ <% content_for :help_section do -%>

    <%= l("help_section_#{name}") %>

    <% end %> ================================================ FILE: app/views/help_sections/_show_popup.html.erb ================================================ ================================================ FILE: app/views/help_sections/edit.html.erb ================================================

    Editing help_section

    <% form_for(@help_section) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @help_section %> | <%= link_to 'Back', help_sections_path %> ================================================ FILE: app/views/help_sections/index.html.erb ================================================

    Listing help_sections

    <% @help_sections.each do |help_section| %> <% end %>
    <%= link_to 'Show', help_section %> <%= link_to 'Edit', edit_help_section_path(help_section) %> <%= link_to 'Destroy', help_section, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New help_section', new_help_section_path %> ================================================ FILE: app/views/help_sections/new.html.erb ================================================

    New help_section

    <% form_for(@help_section) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', help_sections_path %> ================================================ FILE: app/views/help_sections/show.html.erb ================================================

    help section

    <%= @help_section.id %> <%= @help_section.name %> <%= @help_section.show %> <%= link_to 'Edit', edit_help_section_path(@help_section) %> | <%= link_to 'Back', help_sections_path %> ================================================ FILE: app/views/home/_footer.html.erb ================================================ ================================================ FILE: app/views/home/_header.html.erb ================================================ ================================================ FILE: app/views/home/about.html.erb ================================================
    <%= render :partial => 'header', :locals => {:page => 'about'} %>

    This is a little overview about us

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus.

    Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh.

    Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut.

    Beautiful, useful, gorgeous.

    “Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus.”

    This is a great option to post notices to your users, is great for the important information.
    ================================================ FILE: app/views/home/apps.html.erb ================================================
    <%= render :partial => 'header', :locals => {:page => 'about'} %>

    See our latest applications

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    ================================================ FILE: app/views/home/contact.html.erb ================================================
    <%= render :partial => 'header', :locals => {:page => 'about'} %>

    Contact Us or say Hi

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

    Like all true geeks, we love receiving emails. You may just want to say “Hi” or you think I kinda rock and are interested in working with us. Well it’s pretty easy to get in touch, just fill out this handy little form with your info and I will get back to you.

    .

    Contact us if you need further assistance

    ================================================ FILE: app/views/home/elements.html.erb ================================================
    <%= render :partial => 'header', :locals => {:page => 'about'} %>

    CSS Basic Elements

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

    The purpose of this HTML is to help determine what default settings are with CSS and to make sure that all possible HTML Elements are included in this HTML so as to not miss any possible Elements when designing a site.


    Headings

    Heading 1

    Heading 2

    Heading 3

    Heading 4

    Heading 5
    Heading 6
    [top]

    Paragraph

    Lorem ipsum dolor sit amet, test link adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. Donec faucibus. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus.

    Lorem ipsum dolor sit amet, emphasis consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. Donec faucibus. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus.

    [top]

    List Types

    Definition List

    Definition List Title
    This is a definition list division.

    Ordered List

    1. List Item 1
    2. List Item 2
    3. List Item 3

    Unordered List

    • List Item 1
    • List Item 2
    • List Item 3
    [top]

    Fieldsets, Legends, and Form Elements

    Legend

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. Donec faucibus. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus.

    Form Element

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. Donec faucibus. Nunc iaculis suscipit dui.





    Radio 1
    Radio 2
    Radio 3


    Radio 1
    Radio 2
    Radio 3



    [top]

    Tables

    Table Header 1Table Header 2Table Header 3
    Division 1Division 2Division 3
    Division 1Division 2Division 3
    Division 1Division 2Division 3
    [top]

    Misc Stuff - abbr, acronym, pre, code, sub, sup, etc.

    Lorem superscript dolor subscript amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. cite. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. NBA Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus. AVE

    Lorem ipsum dolor sit amet, nisl.NBA Mauris a ante. AVE

    "This stylesheet is going to help so freaking much."
    -Blockquote
    [top]

    Hello World


    Lorem ipsum dolor sit amet, test link adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. Donec faucibus. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus.

    ================================================ FILE: app/views/layouts/gooey.html.erb ================================================ <%=h html_title %> <%= stylesheet_link_tag 'reset-fonts' %> <%= stylesheet_link_tag 'jquery.fancybox-1.3.4' %> <%= stylesheet_link_tag 'jquery.tagsinput' %> <%= stylesheet_link_tag 'jquery-ui-1.8.8.custom' %> <%= stylesheet_link_tag 'gt-styles' %> <%= stylesheet_link_tag 'headerandfooter.css' %> <%= javascript_include_tag :defaults %> <%= javascript_include_tag 'jquery.jgrowl_minimized' %> <%= javascript_include_tag 'jquery.scrollTo-min' %> <%= javascript_include_tag 'jquery.fancybox-1.3.4.pack' %> <%= javascript_include_tag 'jquery.sparkline.min' %> <%= javascript_include_tag 'jquery.tagsinput' %> <%= javascript_include_tag 'store' %> <%= yield :header_tags -%> <%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %> <%= javascript_tag "$(document).ready(function() {initialize();});"%>
    <%= render :partial => "layouts/account_menu" -%>
    <%= yield :buttonbar %>
    <%= page_header_title %>

    <%= page_header_name %><%= sub_workstream_project_box(@project) %>

    <% if (!has_content?(:searchbar)) %> <% else %> <%= yield :searchbar %> <% end %>
    <% if @project && !@project.new_record? %> <%= render_main_menu(@project) %> <% else %> <% if (has_content?(:mainmenu)) %> <%= yield :mainmenu %> <% else %> <% end %> <% end %>
    <% if (has_content?(:actionmenu)) %> <%= yield :actionmenu %> <% end %> <% if (has_content?(:help_section)) %> <% end %>
    <% if (!has_content?(:topbuttons)) %> <% else %> <%= yield :topbuttons %> <% end %>
    <% if (has_content?(:secondnav)) %>
    <%= yield :secondnav %>
    <% end %>
    <% if (has_content?(:actionmenu) && false) cssclass = has_content?(:sidebar) ? 'gt-bd gt-cols-2 clearfix' : 'gt-bd gt-cols-3 clearfix' elsif cssclass = has_content?(:sidebar) ? 'gt-bd gt-cols clearfix' : 'gt-bd clearfix' end %> <%= tag('div', {:id => 'main', :class => cssclass }, true) %>
    <%= render_global_messages %> <%= render_flash_messages %> <%= yield %>
    <% if (has_content?(:sidebar)) %>
    <%= yield :sidebar %>
    <% end %>
    <% if (has_content?(:footer)) %> <%= yield :footer %> <% else %> <%= render :partial => '/home/footer' %> <% end %> ================================================ FILE: app/views/layouts/help_sections.html.erb ================================================ HelpSections: <%= controller.action_name %> <%= stylesheet_link_tag 'scaffold' %>

    <%= flash.now[:success] %>

    <%= yield %> ================================================ FILE: app/views/layouts/issue_blank.html.erb ================================================ <%=h html_title %> <%= stylesheet_link_tag 'reset-fonts' %> <%= stylesheet_link_tag 'gt-styles' %> <%= stylesheet_link_tag 'jquery.fancybox-1.3.4' %> <%= stylesheet_link_tag 'jquery-ui-1.8.8.custom' %> <%= stylesheet_link_tag 'dashboard' %> <%= stylesheet_link_tag 'issue' %> <%= stylesheet_link_tag 'jquery.fileupload-ui' %> <%= stylesheet_link_tag 'jquery.tagsinput' %> <%= javascript_include_tag :defaults %> <%= javascript_include_tag 'jquery.jgrowl_minimized.js' %> <%= javascript_include_tag 'jquery.scrollTo-min' %> <%= javascript_include_tag 'jquery.fancybox-1.3.4.pack' %> <%= javascript_include_tag 'jquery.sparkline.min.js' %> <%= javascript_include_tag 'store' %> <%= javascript_include_tag 'dashboard' %> <%= javascript_include_tag 'jquery.fileupload.js' %> <%= javascript_include_tag 'jquery.fileupload-ui.js' %> <%= javascript_include_tag 'jquery.tagsinput.js' %> <%= yield :header_tags -%> <%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>
    <%= render_flash_messages %> <%= yield %>
    ================================================ FILE: app/views/layouts/mailer.text.html.erb ================================================ <%= yield %>
    <% @footer ||= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer)%> <%= @footer %> ================================================ FILE: app/views/layouts/mailer.text.plain.erb ================================================ <%= yield %> -- <% @footer ||= Setting.emails_footer%> <%= @footer %> ================================================ FILE: app/views/layouts/static.html.erb ================================================ BetterMeans - Open and Democratic Project Management <%= stylesheet_link_tag 'headerandfooter.css' %> <%= stylesheet_link_tag 'static/style.css' %> <%= stylesheet_link_tag 'static/screen.css' %> <%= stylesheet_link_tag 'static/superfish.css' %> <%= stylesheet_link_tag 'gt-rounded-corners.css' %> <%= stylesheet_link_tag 'jquery.fancybox-1.3.4' %> <%= javascript_include_tag :defaults %> <%= javascript_include_tag 'jquery.scrollTo-min' %> <%= javascript_include_tag 'jquery.fancybox-1.3.4.pack' %> <%= javascript_include_tag 'jquery.sparkline.min' %> <%= yield %> <%= render :partial => '/home/footer' %> ================================================ FILE: app/views/mailer/_issue_text_html.html.erb ================================================ <%= link_to "#{issue.tracker.name} ##{issue.id}: #{issue.subject}", issue_url %>
    • <%=l(:field_author)%>: <%= issue.author %>
    • <%=l(:field_status)%>: <%= issue.status %>
    • <%=l(:field_assigned_to)%>: <%= issue.assigned_to %>
    <%= textilizable(issue, :description, :only_path => false) %> -----
    <%= l(:text_issue_edit_footer) %>
    ================================================ FILE: app/views/mailer/_issue_text_plain.html.erb ================================================ <%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> <%= issue_url %> <%=l(:field_author)%>: <%= issue.author %> <%=l(:field_status)%>: <%= issue.status %> <%=l(:field_assigned_to)%>: <%= issue.assigned_to %> <%= issue.description %> -----
    <%= l(:text_issue_edit_footer) %>
    ================================================ FILE: app/views/mailer/account_activated.text.html.html.erb ================================================

    <%= l(:notice_account_activated) %>

    <%= l(:label_login) %>: <%= link_to @login_url, @login_url %>

    ================================================ FILE: app/views/mailer/account_activated.text.plain.html.erb ================================================ <%= l(:notice_account_activated) %> <%= l(:label_login) %>: <%= @login_url %> ================================================ FILE: app/views/mailer/account_activation_request.text.html.html.erb ================================================

    <%= l(:mail_body_account_activation_request, @user.login) %>

    <%= link_to @url, @url %>

    ================================================ FILE: app/views/mailer/account_activation_request.text.plain.html.erb ================================================ <%= l(:mail_body_account_activation_request, @user.login) %> <%= @url %> ================================================ FILE: app/views/mailer/account_information.text.html.html.erb ================================================ <% if @user.auth_source %>

    <%= l(:mail_body_account_information_external, @user.auth_source.name) %>

    <% else %>

    <%= l(:mail_body_account_information) %>:

    • <%= l(:field_login) %>: <%= @user.login %>
    • <%= l(:field_password) %>: <%= @password %>
    <% end %>

    <%= l(:label_login) %>: <%= auto_link(@login_url) %>

    ================================================ FILE: app/views/mailer/account_information.text.plain.html.erb ================================================ <% if @user.auth_source %><%= l(:mail_body_account_information_external, @user.auth_source.name) %> <% else %><%= l(:mail_body_account_information) %>: * <%= l(:field_login) %>: <%= @user.login %> * <%= l(:field_password) %>: <%= @password %> <% end %> <%= l(:label_login) %>: <%= @login_url %> ================================================ FILE: app/views/mailer/attachments_added.text.html.html.erb ================================================ <%= link_to @added_to, @added_to_url %>
      <% @attachments.each do |attachment | %>
    • <%= attachment.filename %>
    • <% end %>
    ================================================ FILE: app/views/mailer/attachments_added.text.plain.html.erb ================================================ <%= @added_to %><% @attachments.each do |attachment | %> - <%= attachment.filename %><% end %> <%= @added_to_url %> ================================================ FILE: app/views/mailer/daily_digest.text.html.html.erb ================================================ Here's the list of system updates for work items you're involved with...

    <% @journals.group_by{|j| j.issue_id}.each_pair do |issue_id, details| %> <% issue = Issue.find(issue_id) %> <%= link_to "#{issue.tracker.name} ##{issue.id}: #{issue.subject}", url_for(:controller => 'projects', :action => 'dashboard', :show_issue_id => issue.id, :only_path => false) %>
      <% details.each do |detail|%> <% journal = Journal.find(detail.journal_id) %> <% for detail in journal.details %>
    • <%= show_detail(detail, true) %>
    • <% end %> <% end %>

    -------------------------------
    <% end %> ================================================ FILE: app/views/mailer/daily_digest.text.plain.html.erb ================================================ Here's the list of system updates for work items you're involved with... <% @journals.group_by{|j| j.issue_id}.each_pair do |issue_id, details| %> <% issue = Issue.find(issue_id) %> <%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> <%= url_for(:controller => 'projects', :action => 'dashboard', :show_issue_id => issue.id, :only_path => false) %> <% details.each do |detail|%> <% journal = Journal.find(detail.journal_id) %> <% for detail in journal.details %> <%= show_detail(detail, true) %> <% end %> <% end %> ------------------------------- <% end %> ================================================ FILE: app/views/mailer/document_added.text.html.html.erb ================================================ <%= link_to @document.title, @document_url %>

    <%= textilizable(@document, :description, :only_path => false) %> ================================================ FILE: app/views/mailer/document_added.text.plain.html.erb ================================================ <%= @document.title %> <%= @document_url %> <%= @document.description %> ================================================ FILE: app/views/mailer/email_update_activation.text.html.html.erb ================================================ Activate your updated email address
    <%= @activation_url %>


    --------------------

    Cheers!
    the folks at bettermeans.com ================================================ FILE: app/views/mailer/email_update_activation.text.plain.html.erb ================================================ Activate your updated email address <%= @activation_url %> -------------------- Cheers! the folks at bettermeans.com ================================================ FILE: app/views/mailer/invitation_add.text.html.html.erb ================================================ <%= @user.name %> is inviting you to work with them on <%= @project.name %>

    <% if @note %> --------------------
    <%= @note %>
    --------------------

    <% end %> To accept their invitation follow this link:
    <%= @invitation_url %>


    Good luck!
    the folks at bettermeans

    open, democratic project management
    http://bettermeans.com ================================================ FILE: app/views/mailer/invitation_add.text.plain.html.erb ================================================ <%= @user.name %> is inviting you to work with them on <%= @project.name %> <% if @note %> -------------------- <%= @note %> -------------------- <% end %> To accept their invitation follow this link: <%= @invitation_url %> Good luck! the folks at bettermeans.com ================================================ FILE: app/views/mailer/invitation_remind.text.html.html.erb ================================================ <%= @user.name %> just sent you a reminder that your'e invited to work with them on <%= @project.name %>

    <% if @note %> --------------------
    <%= @note %>
    --------------------

    <% end %> To accept their invitation follow this link:
    <%= @invitation_url %>


    Good luck!
    the folks at bettermeans

    open, democratic project management
    http://bettermeans.com ================================================ FILE: app/views/mailer/invitation_remind.text.plain.html.erb ================================================ <%= @user.name %> just sent you a reminder that your'e invited to work with them on <%= @project.name %> <% if @note %> -------------------- <%= @note %> -------------------- <% end %> To accept their invitation follow this link: <%= @invitation_url %> Good luck! the folks at bettermeans open, democratic project management http://bettermeans.com ================================================ FILE: app/views/mailer/issue_add.text.html.html.erb ================================================ <%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %>
    <%= render :partial => "issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %> ================================================ FILE: app/views/mailer/issue_add.text.plain.html.erb ================================================ <%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %> ---------------------------------------- <%= render :partial => "issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %> ================================================ FILE: app/views/mailer/issue_edit.text.html.html.erb ================================================ <%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
      <% for detail in @journal.details %>
    • <%= show_detail(detail, true) %>
    • <% end %>
    <%= textilizable(@journal, :notes, :only_path => false) %>
    <%= render :partial => "issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %> ================================================ FILE: app/views/mailer/issue_edit.text.plain.html.erb ================================================ <%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %> <% for detail in @journal.details -%> <%= show_detail(detail, true) %> <% end -%> <%= @journal.notes if @journal.notes? %> ---------------------------------------- <%= render :partial => "issue_text_plain", :locals => { :issue => @issue, :issue_url => @issue_url } %> ================================================ FILE: app/views/mailer/lost_password.text.html.html.erb ================================================

    <%= l(:mail_body_lost_password) %>
    <%= auto_link(@url) %>

    <%= l(:field_login) %>: <%= @token.user.login %>

    ================================================ FILE: app/views/mailer/lost_password.text.plain.html.erb ================================================ <%= l(:mail_body_lost_password) %> <%= @url %> <%= l(:field_login) %>: <%= @token.user.login %> ================================================ FILE: app/views/mailer/message_posted.text.html.html.erb ================================================

    <%=h @message.board.project.name %> - <%=h @message.board.name %>: <%= link_to @message.subject, @message_url %>

    <%= @message.author %> <%= textilizable(@message, :content, :only_path => false) %> -----
    <%= l(:text_issue_edit_footer) %>
    ================================================ FILE: app/views/mailer/message_posted.text.plain.html.erb ================================================ <%= @message_url %> <%= @message.author %> <%= @message.content %> -----
    <%= l(:text_issue_edit_footer) %>
    ================================================ FILE: app/views/mailer/news_added.text.html.html.erb ================================================

    <%= link_to @news.title, @news_url %>

    <%= @news.author.name %> <%= textilizable(@news, :description, :only_path => false) %> ================================================ FILE: app/views/mailer/news_added.text.plain.html.erb ================================================ <%= @news.title %> <%= @news_url %> <%= @news.author.name %> <%= @news.description %> ================================================ FILE: app/views/mailer/personal_welcome.text.html.html.erb ================================================ Hey <%= @name %>,

    I saw that you just signed up for our site. Thanks for trying us out.

    We've been building bettermeans and the open enterprise model for almost two years now, and we're just beginning to share it with a wider audience.

    If you have any questions at all, need a hand with anything, or would like to arrange for a demo - don't hesitate to email us at support@bettermeans.com.
    You can also find some useful information to help get you started here http://help.bettermeans.com

    Thanks!

    Chirag Rabari
    510.457.1670

    p.s. if you run into any issues, or have a new feature that you think we should implement, you can directly add it on our platform workstream here:
    http://bettermeans.com/projects/platform/dashboard ================================================ FILE: app/views/mailer/personal_welcome.text.plain.html.erb ================================================ Hey <%= @name %>, I saw that you just signed up for our site. Thanks for trying us out. We've been building bettermeans and the open enterprise model for almost two years now, and we're just beginning to share it with a wider audience. If you have any questions at all, need a hand with anything, or would like to arrange for a demo - don't hesitate to email us at support@bettermeans.com. You can also find some useful information to help get you started here http://help.bettermeans.com Thanks! Chirag Rabari 510.457.1670 p.s. if you run into any issues, or have a new feature that you think we should implement, you can directly add it on our platform workstream here: http://bettermeans.com/projects/platform/dashboard ================================================ FILE: app/views/mailer/register.text.html.html.erb ================================================

    <%= l(:mail_body_register) %>
    <%= auto_link(@url) %>

    ================================================ FILE: app/views/mailer/register.text.plain.html.erb ================================================ <%= l(:mail_body_register) %> <%= @url %> ================================================ FILE: app/views/mailer/reminder.text.html.html.erb ================================================

    <%= l(:mail_body_reminder, :count => @issues.size, :days => @days) %>

      <% @issues.each do |issue| -%>
    • <%=h issue.project %> - <%=link_to("#{issue.tracker} ##{issue.id}", :controller => 'issues', :action => 'show', :id => issue, :only_path => false)%>: <%=h issue.subject %>
    • <% end -%>

    <%= link_to l(:label_issue_view_all), @issues_url %>

    ================================================ FILE: app/views/mailer/reminder.text.plain.html.erb ================================================ <%= l(:mail_body_reminder, :count => @issues.size, :days => @days) %>: <% @issues.each do |issue| -%> * <%= "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %> <% end -%> <%= @issues_url %> ================================================ FILE: app/views/mailer/test.text.html.html.erb ================================================

    This is a test email sent by Redmine.
    Redmine URL: <%= auto_link(@url) %>

    ================================================ FILE: app/views/mailer/test.text.plain.html.erb ================================================ This is a test email sent by Redmine. Redmine URL: <%= @url %> ================================================ FILE: app/views/mailer/wiki_content_added.text.html.html.erb ================================================

    <%= l(:mail_body_wiki_content_added, :page => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url), :author => h(@wiki_content.author)) %>
    <%=h @wiki_content.comments %>

    ================================================ FILE: app/views/mailer/wiki_content_added.text.plain.html.erb ================================================ <%= l(:mail_body_wiki_content_added, :page => h(@wiki_content.page.pretty_title), :author => h(@wiki_content.author)) %> <%= @wiki_content.comments %> <%= @wiki_content_url %> ================================================ FILE: app/views/mailer/wiki_content_updated.text.html.html.erb ================================================

    <%= l(:mail_body_wiki_content_updated, :page => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url), :author => h(@wiki_content.author)) %>
    <%=h @wiki_content.comments %>

    <%= l(:label_view_diff) %>:
    <%= link_to @wiki_diff_url, @wiki_diff_url %>

    ================================================ FILE: app/views/mailer/wiki_content_updated.text.plain.html.erb ================================================ <%= l(:mail_body_wiki_content_updated, :page => h(@wiki_content.page.pretty_title), :author => h(@wiki_content.author)) %> <%= @wiki_content.comments %> <%= @wiki_content.page.pretty_title %>: <%= @wiki_content_url %> <%= l(:label_view_diff) %>: <%= @wiki_diff_url %> ================================================ FILE: app/views/mails/_inbox.html.erb ================================================

    Inbox

    <% if @mails.size == 0 %> <% else %> <% for mail in @mails %> <% end %> <% end %>
    Del? Sent Sender Sent
    No messages
    <%= check_box_tag "delete[]", mail.id %> <% if mail.read? %> <%= link_to h(mail.subject), user_mail_path(@user, mail) %> <% else %> <%= link_to "#{h(mail.subject)} (unread)", user_mail_path(@user, mail) %> <% end %> <%= link_to h(mail.sender.login), user_path(@user) %> <%=h mail.created_at.to_s(:long) %>
    <%= submit_tag "Delete", :disable_with => l(:button_working) %>
    <%= link_to "Sent", user_mails_path(@user, :mailbox => :sent)%> | <%= link_to "Compose", new_user_mail_path(@user)%> ================================================ FILE: app/views/mails/_sent.html.erb ================================================

    Sent

    <% if @mails.size == 0 %> <% else %> <% for mail in @mails %> <% end %> <% end %>
    Del? Subject To Sent
    No messages
    <%= check_box_tag "delete[]", mail.id %> <%= link_to h(mail.subject), user_mail_path(@user, mail) %> <%= link_to h(mail.recipient.login), user_path(@user) %> <%=h mail.created_at.to_s(:long) %>
    <%= submit_tag "Delete", :disable_with => l(:button_working) %>
    <%= link_to "Inbox", user_mails_path(@user)%> ================================================ FILE: app/views/mails/index.html.erb ================================================ <% form_tag delete_selected_user_mails_path(@user) do %> <% if params[:mailbox] == "sent" %> <%= render :partial => "sent" %> <% else %> <%= render :partial => "inbox" %> <% end %> <% end %> ================================================ FILE: app/views/mails/new.html.erb ================================================ <% form_for @mail, :url => user_mails_path(@user) do |f| %>

    To:
    <%= f.text_field :to %> <%= error_message_on @mail, :to %>

    Subject:
    <%= f.text_field :subject %> <%= error_message_on @mail, :subject %>

    Message
    <%= f.text_area :body %> <%= error_message_on @mail, :body %>

    <%= submit_tag "Send", :disable_with => l(:button_working) %>

    <% end %> ================================================ FILE: app/views/mails/show.html.erb ================================================

    From: <%= @mail.sender == @user ? link_to("You", user_path(@user)) : link_to(h(@mail.sender.login), user_path(@user)) %>

    Received: <%= @mail.created_at.to_s(:long) %>

    To: <%= @mail.recipient == @user ? link_to("You", user_path(@user)) : link_to(h(@mail.recipient.login), user_path(@user)) %>

    Message
    <%=h @mail.body %>

    <% if @mail.recipient == @user %> <%= link_to "Reply", new_user_mail_path(@user, :reply_to => @mail) %> | <% end %> <%= link_to "Inbox", user_mails_path(@user)%>

    ================================================ FILE: app/views/members/autocomplete_for_member.html.erb ================================================ <%= users_check_box_tags 'member[user_ids][]', @users %> ================================================ FILE: app/views/messages/_form.html.erb ================================================ <%= error_messages_for 'message' %> <% replying ||= false %>


    <%= f.text_field :subject, :size => 120, :id => 'message_subject' %> <% if !replying && User.current.allowed_to?(:edit_messages, @project) %> <% end %>

    <% if !replying && !@message.new_record? && User.current.allowed_to?(:edit_messages, @project) %>


    <%= f.select :board_id, @project.boards.collect {|b| [b.name, b.id]} %>

    <% end %>

    <%= f.textile_editor :content, :cols => 80, :rows => 15, :class => 'wiki-edit autocomplete-mentions', :id => 'message_content' %>

    <%= textile_editor_initialize(:framework => :jquery) %>

    <%= render :partial => 'attachments/form' %>

    ================================================ FILE: app/views/messages/_motion_topic.html.erb ================================================

    <%=h @topic.subject %>

    <%= authoring @topic.created_at, @topic.author %>

    <%= textilizable(@topic.content, :attachments => @topic.attachments) %>

    <% unless @replies.empty? %>

    <%= l(:label_reply_plural) %>

    <% @replies.each do |message| %>
    ">
    <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %> <%= link_to(image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit)) if message.editable_by?(User.current) %> <%= link_to(image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete)) if message.destroyable_by?(User.current) %>

    <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :anchor => "message-#{message.id}" } %> - <%= authoring message.created_at, message.author %>

    <%= textilizable message, :content, :attachments => message.attachments %>
    <%= link_to_attachments message, :author => false %>
    <% end %> <% end %> <% if !@topic.locked? && authorize_for('messages', 'reply') %>

    <%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %>

    <% end %> <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% html_title h(@topic.subject) %> ================================================ FILE: app/views/messages/edit.html.erb ================================================

    <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%=h @message.subject %>

    <% form_for :message, @message, :url => {:action => 'edit'}, :html => {:multipart => true, :id => 'message-form'} do |f| %> <%= render :partial => 'form', :locals => {:f => f, :replying => !@message.parent.nil?} %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>

    <%= link_to_remote l(:label_preview), { :url => { :controller => 'messages', :action => 'preview', :board_id => @board }, :method => 'post', :update => 'preview', :with => "$('#message-form').serialize()", :complete => "$('body').scrollTo('#preview')" }, :accesskey => accesskey(:preview) %>

    <% end %>
    ================================================ FILE: app/views/messages/new.html.erb ================================================

    <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%= l(:label_message_new) %>

    <% form_for :message, @message, :url => {:action => 'new'}, :html => {:multipart => true, :id => 'message-form'} do |f| %> <%= render :partial => 'form', :locals => {:f => f} %>
    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %>

    <%= link_to_remote l(:label_preview), { :url => { :controller => 'messages', :action => 'preview', :board_id => @board }, :method => 'post', :update => 'preview', :with => "$('#message-form').serialize()", :complete => "$('body').scrollTo('#preview')" }, :accesskey => accesskey(:preview) %>

    <% end %>
    ================================================ FILE: app/views/messages/show.html.erb ================================================ <%= breadcrumb link_to(l(:label_board_plural), {:controller => 'boards', :action => 'index', :project_id => @project}), link_to(h(@board.name), {:controller => 'boards', :action => 'show', :project_id => @project, :id => @board}) %> <% content_for :sidebar do %>

     

    <%=l(:label_quick_links)%>

    • <%= watcher_tag(@topic, User.current) %>
    • <%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'quote', :id => @topic} }, :class => 'icon icon-comment' %>
    • <%= link_to(l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit') if @message.editable_by?(User.current) %>
    • <%= link_to(l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') if @message.destroyable_by?(User.current) %>
    <% end %>

    <%=h @topic.subject %>

    <%= avatar(@topic.author, :size => '53') %>
    <%= textilizable(@topic.content, :attachments => @topic.attachments) %>
    <%= link_to_attachments @topic, :author => false %>
    <%= link_to_user(@topic.author) %>
    <%= since_tag(@topic.created_at) %> ago

    <% unless @replies.empty? %>

    <%= l(:label_reply_plural) %>

    <% @replies.each do |message| %> <% end %>
    <%= avatar(message.author, :size => '53') %>
    ">

    <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :anchor => "message-#{message.id}" } %>

    <%= textilizable message, :content, :attachments => message.attachments %>
    <%= link_to_attachments message, :author => false %>
    <%= link_to_user(message.author) %>
    <%= since_tag(message.created_at) %> ago
    <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %> <%= link_to(image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit)) if message.editable_by?(User.current) %> <%= link_to(image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete)) if message.destroyable_by?(User.current) %>
    <% end %> <% if !@topic.locked? && authorize_for('messages', 'reply') %>

    <%= toggle_link l(:button_reply), "reply", {:focus => 'message_content', :class => 'gt-btn-gray-large', :second_toggle => 'reply_button', :id => 'reply_button'} %>

    <% else %> <%= link_to l(:button_reply), :controller => "account", :action => "login" %> <% end %> <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% html_title h(@topic.subject) %> ================================================ FILE: app/views/motion_votes/_vote.html.erb ================================================
    <% user_vote = motion.motion_votes.belong_to_user(User.current.id).first %> <% if user_vote.nil? %> <%= l :label_you_havent_voted %> <% else %> <%= l :label_you_voted, :action => user_vote.action_description %> <%= distance_of_time_in_words(Time.now,user_vote.updated_at) %> ago <% end %>
    <% if motion.motion_type == Motion::TYPE_SHARE && User.current.shares.for_project(motion.project_id).empty? %> <%= l(:text_you_dont_have_shares_to_vote) %> <% elsif motion.motion_type == Motion::TYPE_SHARE && motion.active? %>
    <%= link_to_remote button(l(:label_agree),:agree) , :url => {:controller => :motion_votes, :action => :create, :points => 1, :motion_id => motion} if user_vote.nil? || user_vote.points < 1%> <%= link_to_remote button(l(:label_neutral),:neutral) , :url => {:controller => :motion_votes, :action => :create, :points => 0, :motion_id => motion} if user_vote.nil? || user_vote.points != 0%> <%= link_to_remote button(l(:label_disagree),:against) , :url => {:controller => :motion_votes, :action => :create, :points => -1, :motion_id => motion} if user_vote.nil? || user_vote.points > -1%> <% elsif motion.active? %>
    <%= link_to_remote button(l(:label_agree),:agree) , :url => {:controller => :motion_votes, :action => :create, :points => 1, :motion_id => motion} if user_vote.nil? || user_vote.points != 1%> <%= link_to_remote button(l(:label_neutral),:neutral) , :url => {:controller => :motion_votes, :action => :create, :points => 0, :motion_id => motion} if user_vote.nil? || user_vote.points != 0%> <%= link_to_remote button(l(:label_disagree),:against) , :url => {:controller => :motion_votes, :action => :create, :points => -1, :motion_id => motion} if user_vote.nil? || user_vote.points != -1%> <%= link_to_remote button(l(:label_block),:block) , :url => {:controller => :motion_votes, :action => :create, :points => -9999, :motion_id => motion} if user_vote.nil? || user_vote.points != -9999%> <% end %>

    <%= l :label_tally %>

    <% if user_vote.nil? && motion.active? %> <%= l :text_tally_hidden_until_you_vote %> <% else %>
    <%= tally_table(motion) %>
    <% end %>


    <%= l :label_history %>

    <% if user_vote.nil? %> <%= l :text_history_hidden_until_you_vote %> <% else %> <% motion.motion_votes.history.each do |mv| %> <%= !user_vote.isbinding && mv.isbinding ? l(:label_hidden) : link_to_user(mv.user) %>: <%= mv.action_description %> <%= distance_of_time_in_words(Time.now,mv.updated_at) %> ago <%= "(#{l :label_non_binding})" if !mv.isbinding %>
    <% end %> <% end %>
    ================================================ FILE: app/views/motion_votes/cast_vote.js.rjs ================================================ page.replace_html "motion_votes", :partial => "motion_votes/vote", :locals => {:motion => @motion_vote.motion} page["your_vote"].visual_effect :highlight ================================================ FILE: app/views/motion_votes/edit.html.erb ================================================

    Editing motion_vote

    <% form_for(@motion_vote) do |f| %> <%= f.error_messages %>

    <%= f.label :motion_id %>
    <%= f.text_field :motion_id %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :points %>
    <%= f.text_field :points %>

    <%= f.label :isbinding %>
    <%= f.check_box :isbinding %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @motion_vote %> | <%= link_to 'Back', motion_votes_path %> ================================================ FILE: app/views/motion_votes/index.html.erb ================================================

    Listing motion_votes

    <% @motion_votes.each do |motion_vote| %> <% end %>
    translation missing: en, field_motion User translation missing: en, field_points translation missing: en, field_isbinding
    <%=h motion_vote.motion_id %> <%=h motion_vote.user_id %> <%=h motion_vote.points %> <%=h motion_vote.isbinding %> <%= link_to 'Show', motion_vote %> <%= link_to 'Edit', edit_motion_vote_path(motion_vote) %> <%= link_to 'Destroy', motion_vote, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New motion_vote', new_motion_vote_path %> ================================================ FILE: app/views/motion_votes/new.html.erb ================================================

    New motion_vote

    <% form_for(@motion_vote) do |f| %> <%= f.error_messages %>

    <%= f.label :motion_id %>
    <%= f.text_field :motion_id %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :points %>
    <%= f.text_field :points %>

    <%= f.label :isbinding %>
    <%= f.check_box :isbinding %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', motion_votes_path %> ================================================ FILE: app/views/motion_votes/show.html.erb ================================================

    translation missing: en, field_motion: <%=h @motion_vote.motion_id %>

    User: <%=h @motion_vote.user_id %>

    translation missing: en, field_points: <%=h @motion_vote.points %>

    translation missing: en, field_isbinding: <%=h @motion_vote.isbinding %>

    <%= link_to 'Edit', edit_motion_vote_path(@motion_vote) %> | <%= link_to 'Back', motion_votes_path %> ================================================ FILE: app/views/motions/_motions.html.erb ================================================ <% count = 0 %> <% @motions.each do |motion| count = count + 1 %> max_count %>"> <% end %> max_count %>">

    <%= link_to(h(motion.project.name), :controller => 'projects', :action => 'show', :id => motion.project) + ': ' unless @project %> <%= link_to h(motion.title), project_motion_path(motion.project_id,motion) %>
    by <%= link_to_user(motion.author) %> <%= since_tag(motion.created_at) %> ago

    Show <%= count - max_count %> more ...
    ================================================ FILE: app/views/motions/edit.html.erb ================================================

    Editing motion

    <% form_for(@motion) do |f| %> <%= f.error_messages %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :title %>
    <%= f.text_field :title %>

    <%= f.label :description %>
    <%= f.text_area :description %>

    <%= f.label :variation %>
    <%= f.text_field :variation %>

    <%= f.label :params %>
    <%= f.text_area :params %>

    <%= f.label :motion_type %>
    <%= f.text_field :motion_type %>

    <%= f.label :state %>
    <%= f.text_field :state %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @motion %> | <%= link_to 'Back', motions_path %> ================================================ FILE: app/views/motions/eligible_users.html.erb ================================================ <% if @concerned_user_list.length > 0 %>

    <%= :Regarding %>
    <%= collection_select(:motion, :concerned_user_id, @concerned_user_list, :user_id, :name, :prompt => l(:label_select)) %>

    <% else %> <% case @variation when Motion::VARIATION_NEW_MEMBER %>You have no contributors to nominate for membership.<% when Motion::VARIATION_NEW_CORE %>You have no existing members to nominate to the core team.<% when Motion::VARIATION_FIRE_MEMBER %>You have no existing members to remove.<% when Motion::VARIATION_FIRE_CORE %>You have no existing core members to remove.<% end %>
    Click here to learn more about how membership rules work.
    <% end %> ================================================ FILE: app/views/motions/index.html.erb ================================================

    Listing motions

    <% @motions.each do |motion| %> <% end %>
      Title State Vote Type Details
    <%= motion.id %> <%= link_to h(motion.title), project_motion_path(@project,motion) %> <%=l "label_motion_state#{h motion.state}" %> <%=l "label_motion_vote_type#{h motion.motion_type}" %> <%=h motion.description %>

    <% content_for :sidebar do %>
    • <%= link_to 'New motion', new_project_motion_path(@project)%>
    <% end %> ================================================ FILE: app/views/motions/new.html.erb ================================================ <%= help_section("motion_new") %>

    New motion

    <% form_for @motion, :url => {:action => "create", :project_id => @project.id} do |f| %>
    <%= f.error_messages %>

    <%= select(:motion, :variation, Setting::MOTIONS.collect{|m| [ m[1]["Title"], m[0] ] }.sort,:prompt => l(:label_select)) %>

    <%= f.label :description %>
    <%= f.text_area :description %>

    <%= observe_field :motion_variation, :url => {:controller => :motions, :action => :eligible_users, :only_path => :false }, :update => :eligible_users_container, :with => 'variation' %>

    <%= f.submit 'Create' %>

    <% end %> ================================================ FILE: app/views/motions/show.html.erb ================================================

    <%= l :label_motion %> <%= h @motion.id %> : <%=h @topic.subject %>

    <% if @motion.concerned_user_id != nil %> <% end %>
    <%= avatar(@motion.concerned_user, :size => '53') %>
    <%= link_to_user_from_id @motion.concerned_user_id %>
    <%= textilizable (h @motion.description) %>

    State: <%=l "label_motion_state#{h @motion.state}" %>
    Variation: <%=l "label_motion_variation#{h @motion.variation}" %>
    Vote Type: <%=l "label_motion_vote_type#{h @motion.motion_type}" %> (<%=l "label_motion_vote_type#{h @motion.motion_type}_desc" %>)
    Minimum level needed to view: <%="#{h @motion.visibility_level_description}" %>
    Minimum level for binding vote: <%="#{h @motion.binding_level_description}" %>
    Discussion viewable by: Everyone

    by: <%= link_to_user(@topic.author) %>
    Added: <%= since_tag(@topic.created_at) %> ago
    <%= render_title_date %>
    <% unless @replies.empty? %>

    <%= l(:label_comment_plural) %>

    <% @replies.each do |message| %> <% end %>
    <%= avatar(message.author, :size => '53') %>
    ">

    <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :anchor => "message-#{message.id}" } %>

    <%= textilizable message, :content, :attachments => message.attachments %>
    <%= link_to_user(message.author) %>
    <%= since_tag(message.created_at) %> ago
    <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %>
    <% end %> <% if !@topic.locked? && authorize_for('messages', 'reply') %>

    <%= toggle_link l(:button_comment), "reply", {:focus => 'message_content', :class => 'gt-btn-gray-large'} %>

    <% end %> <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% html_title h(@topic.subject) %> <% content_for :sidebar do %>

    <%= render_title_date %>

    <%= render :partial => "motion_votes/vote", :locals => {:motion => @motion} %> <% end %> ================================================ FILE: app/views/my/_belong_to_projects.html.erb ================================================ <% project_tree_sorted(belong_to_projects) do |p, level| name_prefix = (level > 0 ? (' ' * 4 * level + '» ') : '') %> <% end %>
    <%= name_prefix + link_to(h(p), :controller => 'projects', :action => 'show', :id => p) %><%= volunteering p %><%= privacy p %> <% array = p.activity_line_show(30) %> <%= p.activity_line_show(30) %>
    ================================================ FILE: app/views/my/_billing_form.html.erb ================================================

    <%= f.text_field :b_first_name %>

    <%= f.text_field :b_last_name %>

    <%= f.text_field :b_address1 %>

    <%= f.select :b_country, options_for_select(country_hash.sort, :selected => "US") %>

    <%= f.text_field :b_zip %>

    <%= f.text_field :b_phone %>

    Visa MasterCard AmEx Discover

    <%= f.text_field :b_cc_last_four %>

    <%= f.select :b_cc_month, options_for_select(month_hash, :selected => @user.b_cc_month || Date.today.month) %> <%= f.select :b_cc_year, options_for_select(year_hash.sort, :selected => @user.b_cc_year || Date.today.year) %>

    <%= text_field "", "ccverify", :size => 5, :class => "verification-code" %>CVV

    ================================================ FILE: app/views/my/_block.html.erb ================================================
    <%= link_to_remote "", { :url => { :action => "remove_block", :block => block_name }, :complete => "removeBlock('block_#{block_name.dasherize}')" }, :class => "close-icon" %>
    <%= render :partial => "my/blocks/#{block_name}", :locals => { :user => user } %>
    ================================================ FILE: app/views/my/_my_projects.html.erb ================================================ <% project_tree_sorted(my_projects) do |p, level| name_prefix = (level > 0 ? (' ' * 4 * level + '» ') : '') %> <% end %>
    <%=name_prefix + link_to(h(p), :controller => 'projects', :action => 'show', :id => p) %><%= volunteering p %><%= privacy p %> <% if p.archived? %> <% if User.current.allowed_to?({:controller => 'projects', :action => 'unarchive'}, p) %> <%= link_to_remote(l(:label_unarchive_project_brackets), :url => {:controller => 'projects', :action => 'unarchive', :id => p, :table_id => table_id}, :confirm => l(:text_confirm_unarchive_project)) %> <% else %> (archived) <% end %> <% end %> <% if p.locked? %> <%= link_to "(locked)", {:controller => 'my', :action => 'upgrade'} %> <% end %> <%= p.activity_line_show(60) %>
    ================================================ FILE: app/views/my/_plan_description.html.erb ================================================
    • $<%= plan.amount.round %> monthlyCost

    • <%= describe plan.storage_max %> GBStorage

    • <%= describe plan.private_workstream_max %>Private Workstreams

    • <%= describe plan.contributor_max %>Private Collaborators

    • <%= describe plan.public_workstream_max %>Public Workstreams

    • unlimitedPublic Collaborators

    ================================================ FILE: app/views/my/_plan_descriptions.html.erb ================================================
    • Storage Workstreams Contributors Plan

    • <% Plan.find(:all, :order => "CODE asc").each do |plan| %>
    • <%= describe plan.storage_max %> GB <%= describe plan.private_workstream_max %> <%= describe plan.contributor_max %> <%= radio_button(:user,:plan_id, plan.id) %> <%= plan.name %>($<%= plan.amount.round %>/month)

    • <% end %>
    ================================================ FILE: app/views/my/_project_list.html.erb ================================================ <% if my_projects.length > 0 %>
    <%= render :partial => 'my_projects', :locals => {:my_projects => my_projects, :table_id => table_id} %>
    <%= table_bottom_link %>
    <% else %>

    <%= no_data_help %>

    <%= no_data_link %>

    <% end %> ================================================ FILE: app/views/my/_sidebar.html.erb ================================================

    General

    • <%= @user.login %><%=l(:field_login)%>

    • <%= format_time(@user.created_at) %><%=l(:field_created_at)%>

    <%= l(:label_current_plan) %>

    <%= render :partial => "usage_stats" %> <%= upgrade_options User.current %> ================================================ FILE: app/views/my/_usage_stats.html.erb ================================================ <% plan = User.current.plan %>

    <%= plan.name %> Plan

      <% if @user.trial_expires_on && @user.trial_expires_on >= DateTime.now %>
    • <%= distance_of_date_in_words(DateTime.now, @user.trial_expires_on) %> Trial Expires in

    • <% elsif @user.trial_expires_on %>
    • <%= distance_of_date_in_words(DateTime.now, @user.trial_expires_on) %> ago Trial Expired!

    • <% end %>
    • $<%= plan.amount.round %> monthlyCost

    • <%= User.current.project_storage_total %> / <%= describe plan.storage_max %> GBStorage

    • <%= User.current.private_project_total %> / <%= describe plan.private_workstream_max %>Private Workstreams

    • <%= User.current.private_contributor_total %> / <%= describe plan.contributor_max %>Private Collaborators

    • <%= User.current.public_project_total %> / <%= describe plan.public_workstream_max %>Public Workstreams

    • <%= User.current.public_contributor_total %> / unlimitedPublic Collaborators

    ================================================ FILE: app/views/my/account.html.erb ================================================ <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :myaccount} %> <% end %> <%= help_section("my_account") %> <%= error_messages_for 'user' %> <% form_for :user, @user, :url => { :action => "account" }, :builder => TabularFormBuilder, :lang => current_language, :html => { :id => 'my_account_form' } do |f| %>

    <%=l(:label_personal_information)%>

    <%= f.text_field :firstname, :required => true %>

    <%= f.text_field :lastname, :required => true %>

    <%= f.text_field :login, :required => true %>

    <%= f.text_field :mail, :required => true, :readonly => true, :disabled => true %> <%= link_to l(:label_change), :controller => :email_updates, :action => :new %>

    <%= f.select :language, lang_options_for_select %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% if !User.current.b_cc_last_four.nil? %>

    <%=l(:label_billing_information)%>

    <%= render :partial => 'billing_form', :locals => {:f => f} %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %>

    <%=l(:field_mail_notification)%>

    <%= select_tag 'notification_option', options_for_select(@notification_options, @notification_option), :onchange => 'if ($("#notification_option").val() == "selected") {$("#notified-projects").show();} else {$("#notified-projects").hide();alert(ad)}' %> <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>

    <% User.current.projects.each do |project| %>
    <% end %>

    <%= l(:text_user_mail_option) %>

    <% end %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>

    <%=l(:label_preferences)%>

    <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>

    <%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %>

    <% end %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>

    <%=l(:label_your_gravatar)%>

    <%= avatar User.current, :class => "gt-avatar-small" %> <%= l(:text_your_gravatar) %>

    <%=l(:label_data_dump)%>

    <%= l(:text_data_dump) %>   <%= link_to l(:label_issues), {:controller => :issues, :action => :datadump, :format => :csv}, {:class => "icon icon-attachment"} %>

    <% end %> <% content_for :actionmenu do %>
    • <%= link_to l(:button_change_password), {:action => 'password'}, :class => "icon icon-edit" %>
    • <%= link_to l(:label_cancel_account), {:controller => 'account', :action => 'cancel'}, :confirm => l(:text_confirm_cancel_account), :class => "icon icon-del" %>
    <% end %> <% content_for :sidebar do %>

     

    <%= render :partial => 'sidebar' %> <% end %> <% html_title(l(:label_my_account)) -%> ================================================ FILE: app/views/my/blocks/_calendar.html.erb ================================================

    <%= l(:label_calendar) %>

    <% calendar = Redmine::Helpers::Calendar.new(Date.today, current_language, :week) calendar.events = Issue.find :all, :conditions => ["#{Issue.table_name}.project_id in (#{@user.projects.collect{|m| m.id}.join(',')}) AND ((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?))", calendar.startdt, calendar.enddt, calendar.startdt, calendar.enddt], :include => [:project, :tracker, :assigned_to] unless @user.projects.empty? %> <%= render :partial => 'common/calendar', :locals => {:calendar => calendar } %> ================================================ FILE: app/views/my/blocks/_documents.html.erb ================================================

    <%=l(:label_document_plural)%>

    <% project_ids = @user.projects.select {|p| @user.allowed_to?(:view_documents, p)}.collect(&:id) %> <%= render(:partial => 'documents/document', :collection => Document.find(:all, :limit => 10, :order => "#{Document.table_name}.created_at DESC", :conditions => "#{Document.table_name}.project_id in (#{project_ids.join(',')})", :include => [:project])) unless project_ids.empty? %> ================================================ FILE: app/views/my/blocks/_issuesassignedtome.html.erb ================================================ <% if assigned_issues.length > 0 %> <%= render :partial => 'issues/list_very_simple', :locals => { :issues => assigned_issues } %> <% end %> ================================================ FILE: app/views/my/blocks/_issuesreportedbyme.html.erb ================================================

    <%=l(:label_reported_issues)%> (<%= Issue.visible.count(:conditions => { :author_id => User.current.id }) %>)

    <% reported_issues = Issue.visible.find(:all, :conditions => { :author_id => User.current.id }, :limit => 10, :include => [ :status, :project, :tracker ], :order => "#{Issue.table_name}.updated_at DESC") %> <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> <% if reported_issues.length > 0 %>

    <%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :status_id => '*', :author_id => 'me', :sort => 'updated_at:desc' %>

    <% end %> ================================================ FILE: app/views/my/blocks/_issueswatched.html.erb ================================================ <% unless watched_issues.length == 0 %> <%= render :partial => 'issues/list_very_simple', :locals => { :issues => watched_issues } %> <% end %> ================================================ FILE: app/views/my/blocks/_news.html.erb ================================================ <% maxcount ||= 1 if @news && @news.any? %>

    <%=l(:label_news_latest)%>

    <%= render :partial => 'news/news', :locals => {:max_count => maxcount} %>
    <% end %> ================================================ FILE: app/views/my/issues.html.erb ================================================ <%# @page_header_name = "My Items" %> <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :myissues} %> <% end %> <%= render_tabs my_issues_tabs %> <% content_for :actionmenu do %>
    • <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    • <%= link_to l(:label_browse_workstreams), {:controller => 'projects', :action => 'index'}, :class => "icon icon-projects" %>
    <% end %> ================================================ FILE: app/views/my/page.html.erb ================================================
    <%= link_to l(:label_personalize_page), :action => 'page_layout' %>

    <%=l(:label_my_page)%>

    <% @blocks['top'].each do |b| next unless MyController::BLOCKS.keys.include? b %>
    <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
    <% end if @blocks['top'] %>
    <% @blocks['left'].each do |b| next unless MyController::BLOCKS.keys.include? b %>
    <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
    <% end if @blocks['left'] %>
    <% @blocks['right'].each do |b| next unless MyController::BLOCKS.keys.include? b %>
    <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
    <% end if @blocks['right'] %>
    <% content_for :header_tags do %> <%= javascript_include_tag 'context_menu' %> <%= stylesheet_link_tag 'context_menu' %> <% end %> <%= javascript_tag "new ContextMenu('#{url_for(:controller => 'issues', :action => 'context_menu')}')" %> <% html_title(l(:label_my_page)) -%> ================================================ FILE: app/views/my/page_layout.html.erb ================================================
    <% form_tag({:action => "add_block"}, :id => "block-form") do %> <%= select_tag 'block', "" + options_for_select(@block_options), :id => "block-select" %> <%= link_to_remote l(:button_add), {:url => { :action => "add_block" }, :with => "Form.serialize('block-form')", :update => "list-top", :position => :top, :complete => "afterAddBlock();" }, :class => 'icon icon-add' %> <% end %> <%= link_to l(:button_back), {:action => 'page'}, :class => 'icon icon-cancel' %>

    <%=l(:label_my_page)%>

    <% @blocks['top'].each do |b| next unless MyController::BLOCKS.keys.include? b %> <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> <% end if @blocks['top'] %>
    <% @blocks['left'].each do |b| next unless MyController::BLOCKS.keys.include? b %> <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> <% end if @blocks['left'] %>
    <% @blocks['right'].each do |b| next unless MyController::BLOCKS.keys.include? b %> <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> <% end if @blocks['right'] %>
    <%= sortable_element 'list-top', :tag => 'div', :only => 'mypage-box', :handle => "handle", :dropOnEmpty => true, :containment => ['list-top', 'list-left', 'list-right'], :constraint => false, :url => { :action => "order_blocks", :group => "top" } %> <%= sortable_element 'list-left', :tag => 'div', :only => 'mypage-box', :handle => "handle", :dropOnEmpty => true, :containment => ['list-top', 'list-left', 'list-right'], :constraint => false, :url => { :action => "order_blocks", :group => "left" } %> <%= sortable_element 'list-right', :tag => 'div', :only => 'mypage-box', :handle => "handle", :dropOnEmpty => true, :containment => ['list-top', 'list-left', 'list-right'], :constraint => false, :url => { :action => "order_blocks", :group => "right" } %> <%= javascript_tag "updateSelect()" %> ================================================ FILE: app/views/my/password.html.erb ================================================

    <%=l(:button_change_password)%>

    <%= error_messages_for 'user' %> <% form_tag({}, :class => "tabular") do %>

    <%= password_field_tag 'password', nil, :size => 25 %>

    <%= password_field_tag 'new_password', nil, :size => 25 %>
    <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    <%= password_field_tag 'new_password_confirmation', nil, :size => 25 %>

    <%= submit_tag l(:button_apply), :disable_with => l(:button_working) %>
    <% end %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> <% end %> ================================================ FILE: app/views/my/projects.html.erb ================================================ <%# @page_header_name = "My Workstreams" %> <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :myprojects} %> <% end %> <%= render_tabs my_projects_tabs %> <% content_for :actionmenu do %>
    • <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    • <%= link_to l(:label_browse_workstreams), {:controller => 'projects', :action => 'index'}, :class => "icon icon-projects" %>
    <% end %> ================================================ FILE: app/views/my/upgrade.html.erb ================================================ <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :myaccount} %> <% end %> <% form_for :user, @user, :url => { :action => "upgrade" }, :builder => TabularFormBuilder, :lang => current_language, :html => { :id => 'my_upgrade_form' } do |f| %>

    <%= l(:label_current_plan) %>

    <%= render :partial => "usage_stats" %>

    <%=l(:label_select_plan)%>

    <%= render :partial => 'plan_descriptions', :locals => {:current_plan => User.current.plan} %>
    <%= submit_tag l(:button_change_plan), :disable_with => "changing plan..." %>

    <%=l(:label_billing_information)%>

    <%= render :partial => 'billing_form', :locals => {:f => f} %>
    <%= submit_tag l(:button_update_billing), :disable_with => "updating..." %>
    <% end %> ================================================ FILE: app/views/news/_form.html.erb ================================================ <%= error_messages_for 'news' %>

    <%= f.text_field :title, :required => true, :size => 60 %>

    <%= f.text_area :summary, :cols => 60, :rows => 2, "autocomplete-mentions-projectid" => @project.id %>

    <%= f.textile_editor :description, :required => true, :cols => 60, :rows => 15, :class => 'wiki-edit', "autocomplete-mentions-projectid" => @project.id %>

    ================================================ FILE: app/views/news/_news.html.erb ================================================ <% count = 0 %> <% @news.each do |news_item| count = count + 1 %> max_count %>"> <% end %> max_count %>">
      <%= link_to_user(news_item.author) %>, <%= since_tag(news_item.created_at) %> ago <% if news_item.comments_count > 0 %> , <%=link_to l(:label_x_comments, :count => news_item.comments_count), :controller => 'news', :action => 'show', :id => news_item.id %> <% end %>

    <%= link_to(h(news_item.project.name), :controller => 'projects', :action => 'show', :id => news_item.project) + ': ' unless @project %> <%= link_to h(news_item.title), :controller => 'news', :action => 'show', :id => news_item.id %> <%=h news_item.summary %>

    Show <%= count - max_count %> more ...
    ================================================ FILE: app/views/news/edit.html.erb ================================================

    <%=l(:label_news)%>

    <% labelled_tabular_form_for :news, @news, :url => { :action => "edit" }, :html => { :id => 'news-form' } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>

    <%= link_to_remote l(:label_preview), { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" }, :accesskey => accesskey(:preview) %>

    <% end %>
    ================================================ FILE: app/views/news/index.html.erb ================================================ <%= help_section("project_news") %>

    <%=l(:label_news_plural)%>

    <% if @newss.empty? %>

    <%= l(:label_no_data) %>

    <% else %> <% @newss.each do |news| %> <% end %>

    <%= link_to(h(news.project.name), :controller => 'projects', :action => 'show', :id => news.project) + ': ' unless news.project == @project %> <%= link_to h(news.title), :controller => 'news', :action => 'show', :id => news %> <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %>

    <%= textilizable(news.description) %>
    <%= link_to_user_from_id(news.author) %>
    <%= since_tag(news.created_at) %> ago

    <%= pagination_links_full @news_pages %>

    <% end %>
    <% content_for :actionmenu do %>
    • <%= link_to_if_authorized(l(:label_news_new), {:controller => 'news', :action => 'new', :project_id => @project}, :class => 'icon icon-add', :onclick => '$("#add-news").show(); $("#news_title").focus(); return false;') if @project %>
    <% end %> <% html_title(l(:label_news_plural)) -%> ================================================ FILE: app/views/news/new.html.erb ================================================

    <%=l(:label_news_new)%>

    <% labelled_tabular_form_for :news, @news, :url => { :controller => 'news', :action => 'new', :project_id => @project }, :html => { :id => 'news-form' } do |f| %> <%= render :partial => 'news/form', :locals => { :f => f } %> <%= textile_editor_initialize(:framework => :jquery) %>
    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %>

    <%= link_to_remote l(:label_preview), { :url => { :controller => 'news', :action => 'preview', :project_id => @project }, :method => 'post', :update => 'preview', :with => "Form.serialize('news-form')" }, :accesskey => accesskey(:preview) %>

    <% end %>
    ================================================ FILE: app/views/news/show.html.erb ================================================

    <%=h @news.title %>

    <% if authorize_for('news', 'edit') %> <% end %>
    <%= avatar(@news.author, :size => '53') %>

    <% unless @news.summary.blank? %><%="Summary: #{h @news.summary}" %>
    <% end %>

    <%= textilizable(@news.description) %>
    <%= link_to_user(@news.author) %>
    <%= since_tag(@news.created_at) %> ago

    <% if @comments.any? %>

    <%= l(:label_comment_plural) %>

    <% @comments.each do |comment| %> <% next if comment.new_record? %> <% end %>
    <%= avatar(comment.author, :size => '53') %>
    "> <%= textilizable comment.comments %>
    <%= link_to_user(comment.author) %>
    <%= since_tag(comment.created_at) %> ago
    <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'news', :action => 'destroy_comment', :id => @news, :comment_id => comment}, :confirm => l(:text_are_you_sure), :method => :post, :title => l(:button_delete) %>
    <% end %> <% if authorize_for 'news', 'add_comment' %>

    <%= toggle_link l(:label_comment_add), "add_comment_form", {:focus => "comment_comments", :class => "gt-btn-gray-large"} %>

    <% form_tag({:action => 'add_comment', :id => @news}, :id => "add_comment_form", :style => "display:none;") do %>
    <%= textile_editor 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit', "autocomplete-mentions-projectid" => @project.id %>
    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <%= textile_editor_initialize(:framework => :jquery) %> <% end %> <% end %> <% html_title @news.title -%> <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% content_for :sidebar do %>
    • <%= link_to_if_authorized l(:button_edit), {:controller => 'news', :action => 'edit', :id => @news}, :class => 'icon icon-edit', :accesskey => accesskey(:edit), :onclick => '$("#edit-news").show(); return false;' %>
    • <%= link_to_if_authorized l(:button_delete), {:controller => 'news', :action => 'destroy', :id => @news}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
    <% end %> ================================================ FILE: app/views/notifications/_credits_distributed.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to "#{l("label_youve_got_credits")}", {:controller => "projects", :action => "credits", :id => enterprise_id},{:target => "_blank"} %>

    <%= l(:text_credits_distributed_for, :amount => number_to_currency(credit_distribution["amount"].to_f.round, :unit => '', :separator => ".", :delimiter => ",", :precision => 0), :workstream => project_name) %> <%= "

    " + l(:text_credits_are_a_gift) if credit_distribution["retro_id"] == CreditDistribution::GIFT %> <%= "

    " + l(:text_credits_are_an_expense) if credit_distribution["retro_id"] == CreditDistribution::EXPENSE %>

    <%= link_to(l(:button_view), {:controller => "projects", :action => "credits", :id => enterprise_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer', :target => "_blank"})%>   <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_credits_transferred.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to "#{l("label_youve_got_credits")}", {:controller => "projects", :action => "credits", :id => project.id},{:target => "_blank"} %>

    <%= l(:text_credits_transferred, :amount => number_to_currency(amount.to_f.round, :unit => '', :separator => ".", :delimiter => ",", :precision => 0), :workstream => h(project.name), :sender => link_to_user(@notification.sender), :project => link_to_project(project)) %>
    <% unless note.nil? %> ------- <%= l(:text_included_note) %> -------
    <%= h(note) %>
    <% end %>

    <%= link_to(l(:button_view), {:controller => "projects", :action => "credits", :id => project.id}, {:id =>'cr_button', :class => 'icon icon-cr-offer', :target => "_blank"})%>   <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_invitation.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to l("label_new_invitation", :role_name => role_name, :project_name => project_name), {:controller => "projects", :action => "team", :id => project_id},{:target => "_blank"} %>

    <%= l(:text_you_are_invited) %> <%= link_to project_name, {:controller => "projects", :action => "show", :id => project_id} %>.

    <%= l("text_role_description_#{role_name}", :enterprise => project_name) %>

    <%= link_to(l(:button_view), {:controller => "projects", :action => "team", :id => project_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer', :target => "_blank"})%>   <%= link_to(l(:button_accept_invitation), {:controller => "invitations", :action => "accept", :id => @notification.source_id, :token => token}, {:id =>'cr_button', :class => 'icon icon-cr-accept'})%>   <%= link_to_remote(l(:button_ignore), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_ignored}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_issue_joined.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= l("label_someone_joined_your_issue", :joiner => link_to_user_from_id(@notification.sender_id)) %>

    <%= l(:text_joiner_is_now_working_with_you, :joiner => link_to_user_from_id(@notification.sender_id), :issue => link_to_issue_from_id(issue_id)) %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_issue_left.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= l("label_someone_left_your_issue", :joiner => link_to_user(joiner)) %>

    <%= l(:text_joiner_is_no_longer_working_with_you, :joiner => link_to_user(joiner), :issue => link_to_issue(issue)) %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_issue_team_member_added.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= l("label_someone_added_you_to_their_issue", :sender => link_to_user_from_id(@notification.sender_id)) %>

    <%= l(:text_you_are_now_working_with_you, :issue => link_to_issue_from_id(issue_id)) %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_issue_team_member_removed.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= l("label_someone_removed_you_from_their_issue", :sender => link_to_user_from_id(@notification.sender_id)) %>

    <%= l(:text_you_are_no_longer_working_on, :issue => link_to_issue_from_id(issue_id)) %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_mention.html.erb ================================================
    <%= avatar_from_id(@mention.sender_id, :size => '53') %>
    <%= link_to_user_from_id(@mention.sender_id) %>
    ago <%= link_to (truncate(title,50),url, {:class => "#{@mention.source_type == 'Issue' || @mention.source_type == 'Journal(Issue)' ? 'fancyframe' : ''}"}) %>

    <%= make_expandable mention_text %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @mention.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%> <%= link_to(l(:button_view), url, {:id =>'cr_button', :class => "icon icon-cr-offer #{@mention.source_type == 'Issue' || @mention.source_type == 'Journal(Issue)' ? 'fancyframe' : ''}", :target => "_blank"})%>

    ================================================ FILE: app/views/notifications/_message.html.erb ================================================
    <% unless sender_id.nil? %> <% end %>
    <%= avatar_from_id(sender_id) %>

    <%= subject %>

    <%= l(:label_by, :author => link_to_user_from_id(sender_id), :age => time_tag(notification_created_at)) %>
    <%= message %>

    <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => notification_id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>
    ================================================ FILE: app/views/notifications/_motion_started.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to l("label_motion_started", :title => motion_title, :id => @notification.source_id), {:controller => "motions", :action => "show", :id => @notification.source_id, :project_id => enterprise_id},{:target => "_blank"} %>

    <%= link_to(l(:button_participate), {:controller => "motions", :action => "show", :id => @notification.source_id, :project_id => enterprise_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer', :target => "_blank"})%> <%= link_to_remote(l(:button_ignore), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_ignored}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_new_role.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to l("label_new_role", :role_name => role_name), {:controller => "projects", :action => "team", :id => enterprise_id},{:target => "_blank"} %>

    <%= l(:text_you_are_now_a_role, :role_name => role_name) %> <%= link_to project_name, {:controller => "projects", :action => "show", :id => enterprise_id} %>.

    <%= l("text_role_description_#{role_name}", :enterprise => project_name) %>

    <%= link_to(l(:button_view), {:controller => "projects", :action => "team", :id => enterprise_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer', :target => "_blank"})%>   <%= link_to_remote(l(:button_ignore), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_ignored}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_retro_ended.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to "#{l("label_retro_ended")} ##{@notification.source_id}", {:controller => "retros", :action => "show", :id => @notification.source_id},{:class => "fancyframe"} %>

    <%= l(:text_retro_ended_for) %> <%= project.name %> <%= l(:text_retro_ended) %>

    <%= link_to(l(:button_view), {:controller => "retros", :action => "show", :id => @notification.source_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer fancyframe', :target => "_blank"})%>   <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_retro_started.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to "#{l("label_retro_started")} ##{@notification.source_id}", {:controller => "retros", :action => "show", :id => @notification.source_id},{:class => "fancyframe"} %>

    <%= l(:text_retro_started_for) %> <%= project.name %> <%= l(:text_retro_started) %>

    <%= link_to(l(:button_participate), {:controller => "retros", :action => "show", :id => @notification.source_id}, {:id =>'cr_button', :class => 'icon icon-cr-offer fancyframe', :target => "_blank"})%>   <%= link_to_remote(l(:button_ignore), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_ignored}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_trial_expired.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to l(:label_trial_expired), {:controller => "my", :action => "upgrade"} %>

    <%= l(:text_trial_expired) %>

    <%= link_to(l(:button_update_billing), {:controller => "my", :action => "upgrade"}, {:id =>'cr_button', :class => 'icon icon-cr-offer'})%>   <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/_unresponded.html.erb ================================================ <% if Notification.unresponded? %> <%=link_to pluralize(Notification.unresponded_count,l(:label_new_notification)), {:controller => 'notifications', :action => 'index'} %> | <% end %> ================================================ FILE: app/views/notifications/_usage_over.html.erb ================================================ <%= avatar_from_id(@notification.sender_id, :size => '53') %>

    <%= link_to l(:label_usage_over), {:controller => "my", :action => "upgrade"} %>

    <%= l(:text_usage_over) %>

    <%= link_to(l(:button_upgrade), {:controller => "my", :action => "upgrade"}, {:id =>'cr_button', :class => 'icon icon-cr-offer'})%>   <%= link_to_remote(l(:button_mark_as_read), {:url => {:controller => "notifications", :action => "hide", :notification_id => @notification.id, :message => :label_marked_as_read}}, {:id =>'cr_button', :class => 'icon icon-cr-ignore'})%>

    <%= link_to_user_from_id(@notification.sender_id) %>
    <%= since_tag(@notification.created_at) %> ago
    ================================================ FILE: app/views/notifications/edit.html.erb ================================================

    Editing notification

    <% form_for(@notification) do |f| %> <%= f.error_messages %>

    <%= f.label :recipient_id %>
    <%= f.text_field :recipient_id %>

    <%= f.label :partial_name %>
    <%= f.text_field :partial_name %>

    <%= f.label :params %>
    <%= f.text_field :params %>

    <%= f.label :response %>
    <%= f.text_field :response %>

    <%= f.label :expiration_date %>
    <%= f.date_select :expiration_date %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @notification %> | <%= link_to 'Back', notifications_path %> ================================================ FILE: app/views/notifications/error.js.rjs ================================================ page.alert "Couldn't send your response to this notification!" ================================================ FILE: app/views/notifications/hide.js.rjs ================================================ #Checking if this update is being called through the notification page if (!params[:notification_id].nil?) page.replace "notification_" + params[:notification_id], content_tag(:tr, content_tag(:td, l(params[:message] || :label_response_sent), :class => "notification_response", :colspan => 3, :id => "notification_" + params[:notification_id])) page.replace "notification", :partial => "notifications/unresponded" page["notification_" + params[:notification_id]].visual_effect :highlight end ================================================ FILE: app/views/notifications/index.html.erb ================================================ <%# @page_header_name = "My Notifications" %> <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :notifications} %> <% end %>

    <%=l(:label_notifications)%>

    <% if @notifications.empty? %>

    <%= l(:label_no_data) %>

    <% else %> <% @notifications.each do |@notification| %> <%= render :partial => @notification.variation, :locals => @notification.params %> <% end %> <% end %>
    <% content_for :sidebar do %>

    <%=l(:label_mentions)%> <%= help_bubble(:help_mentions,{:login => User.current.login}) %>

    <% if @mentions.empty? %>

    <%= l(:label_no_mention) %>

    <% else %> <% @mentions.each do |@mention| %> <%= render :partial => @mention.variation, :locals => @mention.params %> <% end %> <% end %>
    <% end %> ================================================ FILE: app/views/notifications/new.html.erb ================================================

    New notification

    <% form_for(@notification) do |f| %> <%= f.error_messages %>

    <%= f.label :recipient_id %>
    <%= f.text_field :recipient_id %>

    <%= f.label :partial_name %>
    <%= f.text_field :partial_name %>

    <%= f.label :params %>
    <%= f.text_field :params %>

    <%= f.label :response %>
    <%= f.text_field :response %>

    <%= f.label :expiration_date %>
    <%= f.date_select :expiration_date %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', notifications_path %> ================================================ FILE: app/views/notifications/show.html.erb ================================================

    translation missing: en, field_recipient: <%=h @notification.recipient_id %>

    translation missing: en, field_partial_name: <%=h @notification.partial_name %>

    translation missing: en, field_params: <%=h @notification.params %>

    translation missing: en, field_response: <%=h @notification.response %>

    translation missing: en, field_expiration_date: <%=h @notification.expiration_date %>

    <%= link_to 'Edit', edit_notification_path(@notification) %> | <%= link_to 'Back', notifications_path %> ================================================ FILE: app/views/projects/_active_member_box.html.erb ================================================ <% @members_active = @project.active_members if @members_active.any? %>

    <%=l(:label_active_team)%><%= help_bubble(:text_active_team_explanation) %>

    <% @members_active.each do |member| %> <% end %> <% if User.current.allowed_to?(:send_invitations, @project) && @project.root? %> <% end %> <% unless @hide_view_team_link %> <% end %>
    <%= avatar(member.user) %>

    <%= link_to_user member.user %>

    <% roles = member.user.roles_for_project(@project).collect{|role| role.name } roles.delete(Role.active.name) %> <%= roles.join(",") %>
    <%= link_to l(:label_invitation_others), new_project_invitation_url(@project) %>
    <%= link_to l(:label_team_view_all_enterprise), {:controller => 'projects', :action => 'team', :id => @project.root} %>
    <% end %> ================================================ FILE: app/views/projects/_active_member_box_simple.html.erb ================================================ <% @members_active = @project.active_members if @members_active.any? %>

    <%=l(:label_active_team)%><%= help_bubble(:text_active_team_explanation) %>

    <% if User.current.allowed_to?(:send_invitations, @project) && @project.root? %> <% end %> <% unless @hide_view_team_link %> <% end %>
    <% @members_active.each do |member| %>
    <%= avatar(member.user) %>
    <%= link_to_user member.user, {:format => :firstname} %>
    <% end %>
    <%= link_to l(:label_invitation_others), new_project_invitation_url(@project) %>
    <%= link_to l(:label_team_view_all_enterprise), {:controller => 'projects', :action => 'team', :id => @project.root} %>
    <% end %> ================================================ FILE: app/views/projects/_clearance_member_box.html.erb ================================================ <% @members_active = @project.clearance_members if @members_active.any? %>

    <%=l(:label_team_with_access)%>

    <% @members_active.each do |member| %> <% end %> <% if User.current.allowed_to?(:send_invitations, @project) && @project.root? %> <% end %>
    <%= avatar(member.user) %>

    <%= link_to_user member.user %>

    <%= link_to l(:label_invitation_others), new_project_invitation_url(@project) %>
    <%= link_to l(:label_team_view_all_enterprise), {:controller => 'projects', :action => 'team', :id => @project.root} , :class => "gt-btn-blue-large" %>
    <% end %> ================================================ FILE: app/views/projects/_dashboard_javascript_variables.html.erb ================================================ ================================================ FILE: app/views/projects/_edit.html.erb ================================================ <% labelled_tabular_form_for :project, @project, :url => { :action => "edit", :id => @project }, :html => { :multipart => true } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/projects/_form.html.erb ================================================ <%= error_messages_for 'project' %> <% include_modules ||= false %>
    <%= hidden_field_tag 'parent_id', params[:parent_id]%> <% unless @parent.nil? %>

    <% end %>

    <%= f.text_field :name, :required => true %>
    <%= l(:text_caracters_maximum, 50) %>

    <%= f.textile_editor :description, :rows => 5, :class => 'wiki-edit' %>

    <%= textile_editor_initialize(:framework => :jquery) %> <% if @allow_logo_selection %>

    <%= image_tag formatted_project_path(@project, :png) if @project.has_image? %> <%= f.file_field :image_file %>
    <%= f.text_field :image_file_url , :style => "width:60%;"%>

    <% end %>

    <% if include_modules then%>

    <% Redmine::AccessControl.available_project_modules.each do |m| %> <% end %>

    <%= f.select(:dpp, options_for_select(Setting::PAY_SCALES, :selected => @project.dpp.nil? ? Setting::PAY_SCALES_DEFAULT : @project.dpp.round), { :include_blank => false}, { :onchange => "#{remote_function(:url => {:action => "update_scale"}, :with => "'dpp='+value")}"})%> <%= help_bubble :help_select_scale %> <%= render :partial => 'point_scale', :locals => {:unit => unit_for(@project), :dpp => @project.dpp.nil? ? Setting::PAY_SCALES_DEFAULT : @project.dpp.round } %>

    <% end %>
    ================================================ FILE: app/views/projects/_member_box.html.erb ================================================ <% @users_by_role = @project.root.users_by_role if @users_by_role.any? %>

    <%=l(:label_team)%>

    <% @users_by_role.keys.sort.each do |role| %> <% next if role.level != Role::LEVEL_ENTERPRISE %> <% end %>
    <%=h role %><%= help_bubble("text_role_invitation_description_#{role.name.gsub(/ /,'_')}") %> <%= make_expandable(@users_by_role[role].sort.collect{|u| link_to_user u}.join(", "), 250) %>
    <% end %> ================================================ FILE: app/views/projects/_point_scale.html.erb ================================================ <% for i in 0..Setting::POINT_FACTOR.length - 1 do %> <%= i %> Points <%= (Integer(dpp) * Setting::POINT_FACTOR[i]).round %>
    <% end %>
    ================================================ FILE: app/views/projects/_project_list.html.erb ================================================ <% for project in projects %> <% end %>
    <%= render :partial => 'project_summary', :locals => {:project => project} %>
    <%= link_to_remote "... load more ...", { :url => {:controller => :projects, :action => "index_#{index_type}", :offset => offset }, :method => :get }, {:id => "#{index_type}_load_more"} unless projects.last.nil? %>
    ================================================ FILE: app/views/projects/_project_summary.html.erb ================================================

    <%= link_to h(project.name), :controller => 'projects', :action => 'show', :id => project %><%= volunteering project %><%= privacy project %>

    <%= project.all_members.count %>  <%= since_tag(project.created_at) %> ago

    <%= make_expandable((textilizable project.description, :project => project),250) %>

    <%= project.activity_line_show(60) %>
    ================================================ FILE: app/views/projects/_projects_list_simple.html.erb ================================================ <% count = 0 max_count ||= 5 %> <% project_tree_sorted(projects) do |p, level| name_prefix = (level > 0 ? (' ' * 4 * level + '» ') : '') count = count + 1 %> max_count %>"> <% end %> max_count %>">
    <%=name_prefix + link_to(h(p), :controller => 'projects', :action => 'show', :id => p) %><%= volunteering p %><%= privacy p %> <% if !p.active? %> <%= link_to_remote(l(:label_unarchive_project_brackets), :url => {:controller => 'projects', :action => 'unarchive', :id => p}, :confirm => l(:text_confirm_unarchive_project)) %> <% end %> <%= p.activity_line_show(30) %>
    Show <%= count - max_count %> more ...
    ================================================ FILE: app/views/projects/_subprojects.html.erb ================================================ <% subprojects.each do |p| %> <%= ("  » " * level) + link_to(h(p), :action => 'show', :id => p) %><%= volunteering p %><%= privacy p %> <% array = p.activity_line_show(30) %> <%= p.activity_line_show(30) %> <% @subprojects = p.children.active.find(:all, :order => 'name ASC') %> <%= render :partial => 'subprojects', :locals => {:subprojects => @subprojects, :level => level + 1} %> <% end %> ================================================ FILE: app/views/projects/_team_link.html.erb ================================================ ================================================ FILE: app/views/projects/_team_list.html.erb ================================================

    <%=l(:label_team)%>

    <% members = project.enterprise_members.find(:all, :include => [:roles, :user]).sort %> <% if members.any? %>
    <% members.each do |member| %> <% end %> <% if User.current.allowed_to?(:send_invitations, project) && project.root? %> <% end %>
    <%= avatar(member.user) %>

    <%= link_to_user member.user %>

    <%= nomination_links(member,project) %>
    Role: <%=h member.roles.sort.collect(&:to_s).join(', ') %>
    Since: <%= since_tag(member.created_at) %> ago
    <%= link_to l(:label_invitation_others), new_project_invitation_url(project) %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %> ================================================ FILE: app/views/projects/_team_list_member.html.erb ================================================ <%= avatar(member.user, :size => "64") %> <%= link_to_user member.user %> <%=h member.roles.sort.collect(&:to_s).join(', ') %> <%= nomination_links(member,@project) %> ================================================ FILE: app/views/projects/activity.html.erb ================================================ <% @page_header_name = l(:label_browse_activity) %> <%= help_section("project_activity") %>

    <%= @author.nil? ? "" : l(:label_user_activity, link_to_user(@author)) %>
    <%= @project.activity_line_show(60) if @project %>

    <%= render :partial => 'activity_streams/activity_stream_list', :locals => { :user_id => params[:user_id], :project_id => @project ? @project.id : nil, :with_subprojects => params[:with_subprojects], :limit => 50, :max_created_at => nil } %>
    <% content_for :actionmenu do %>
    • <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    • <%= link_to l(:label_browse_workstreams), {:controller => 'projects', :action => 'index'}, :class => "icon icon-projects" %>
    <% end %> <% content_for :sidebar do %> <% end %> <% html_title(l(:label_activity), @author) -%> ================================================ FILE: app/views/projects/add.html.erb ================================================ <% @page_header_name = @parent ? l(:label_subproject_new) : l(:label_project_new) %> <% labelled_tabular_form_for :project, @project, :url => { :action => "add" }, :html => { :multipart => true } do |f| %> <%= render :partial => 'form', :locals => { :f => f, :include_modules => true } %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <%= javascript_tag "$('#project_name').focus();" %> <% end %> <% content_for :sidebar do %> <%= help_section("project_new") %> <% end %> ================================================ FILE: app/views/projects/add_file.html.erb ================================================

    <%=l(:label_attachment_new)%>

    <%= error_messages_for 'attachment' %>
    <% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>

    <%= render :partial => 'attachments/form' %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/projects/copy.html.erb ================================================

    <%=l(:label_project_new)%>

    <% labelled_tabular_form_for :project, @project, :url => { :action => "copy" } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= l(:label_module_plural) %> <% Redmine::AccessControl.available_project_modules.each do |m| %> <% end %>
    <%= l(:button_copy) %> <%= hidden_field_tag 'only[]', '' %>
    <%= submit_tag l(:button_copy) %>
    <% end %> ================================================ FILE: app/views/projects/core_vote.js.rjs ================================================ page.replace @member.user.login, :partial => 'team_list_member', :locals => {:member => @member} ================================================ FILE: app/views/projects/credits.html.erb ================================================ <% content_for :sidebar do %> <%= project_image @project %> <% if !@project.is_public %>

    <%= privacy @project %>This is a private workstream <%= help_bubble :help_this_workstream_is_private %>

    <% end %> <% if @project.volunteer? %>

    <%= volunteering @project %>This is a volunteer-run workstream<%= help_bubble :help_volunteer %>

    <% end %>
    <% if params[:with_subprojects] == 'false' %>

    Sub Workstreams Excluded

    <% else %>

    Sub Workstreams Included

    <% end %>
    • <% if params[:with_subprojects] == 'false' %> <%= link_to "Include sub workstreams", { :controller => 'projects', :action => 'credits', :with_subprojects => :true } %> <% else %> <%= link_to "Exclude sub workstreams", { :controller => 'projects', :action => 'credits', :with_subprojects => :false } %> <% end %>
    • <%= link_to l(:label_credit_transfer), {:controller => 'credit_transfers', :action => 'index', :selected_project_id => @project.id} %>
    <% end %> <%= help_section("project_credits") %>
    <%= render :partial => 'credits/credit_queue'%>
    <%= render :partial => 'credits/credit_history'%>
    <% if authorize_for(:credits, :create) %> <%= link_to 'New credit', new_credit_path %> <% end %> <% content_for :sidebar do %>
    <%= render :partial => 'credits/my_credits'%>


    <%=l(:label_credits_breakdown)%>

    <%= render :partial => 'credits/credit_breakdown', :locals => {:group_credits => @active_credits, :title => l(:label_active_credits)}%>

    <%= render :partial => 'credits/credit_breakdown', :locals => {:group_credits => @oustanding_credits, :title => l(:label_outstanding_credits)}%>

    <%= render :partial => 'credits/credit_breakdown', :locals => {:group_credits => @total_credits, :title => l(:label_total_credits_issued)}%> <% end %> <% html_title(l(:label_credits)) %> ================================================ FILE: app/views/projects/dashboard.html.erb ================================================ <%= javascript_include_tag 'jquery.ui.position' %> <%= stylesheet_link_tag 'dashboard' %> <%= javascript_include_tag 'dashboard' %> <%= javascript_include_tag 'json2.js' %> <%= javascript_include_tag 'jquery.fileupload.js' %> <%= javascript_include_tag 'jquery.fileupload-ui.js' %> <%= stylesheet_link_tag 'jquery.fileupload-ui' %>
    <%= help_section("project_dashboard",true) %>
    <%= l(:text_loading_dashboard) %>
    <% content_for :secondnav do %>
    <% if @project.children.any? %>
    <% end %>
    <% end %> <% content_for :actionmenu do -%> <% end -%> <%# content_for :buttonbar do %> <%# end %> <%= render :partial => 'projects/dashboard_javascript_variables', :locals => {:project => @project} %>
    <% content_for :footer do %> <% end %> ================================================ FILE: app/views/projects/index.html.erb ================================================ <% @page_header_name = l(:label_browse_workstreams) %> <%= help_section "browse_workstreams" %>
    <% if @latest_enterprises.any? %>

    <%=l(:label_enterprise_latest)%>

    <%= render :partial => 'project_list', :locals => {:projects => @latest_enterprises, :offset => 0, :index_type => 'latest', :offset => 10} %>
    <% end %>
    <% if @active_enterprises.any? %>

    <%=l(:label_enterprise_active)%>

    <%= render :partial => 'project_list', :locals => {:projects => @active_enterprises, :offset => 0, :index_type => 'active', :offset => 10} %>
    <% end %>
    <% content_for :actionmenu do %>
    • <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    <% end %> <% html_title(l(:label_project_plural)) -%> ================================================ FILE: app/views/projects/list_files.html.erb ================================================

    <%=l(:label_attachment_plural)%>

    <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %> <%= sort_header_tag('filename', :caption => l(:field_filename)) %> <%= sort_header_tag('created_at', :caption => l(:label_date), :default_order => 'desc') %> <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %> <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %> <% @containers.each do |container| %> <% next if container.attachments.empty? -%> <% container.attachments.each do |file| %> "> <% end reset_cycle %> <% end %>
    MD5
    <%= link_to_attachment file, :download => true, :title => file.description %> <%= format_time(file.created_at) %> <%= number_to_human_size(file.filesize) %> <%= file.downloads %> <%= file.digest %> <%= link_to(image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => file}, :confirm => l(:text_are_you_sure), :method => :post) if delete_allowed %>
    <% html_title(l(:label_attachment_plural)) -%> <% content_for :sidebar do %>
    • <%= link_to_if_authorized l(:label_attachment_new), {:controller => 'projects', :action => 'add_file', :id => @project}, :class => 'icon icon-add' %>
    <% end %> ================================================ FILE: app/views/projects/list_members.html.erb ================================================

    <%=l(:label_team)%>

    <% if @members.empty? %>

    <%= l(:label_no_data) %>

    <% end %> <% members = @members.group_by {|m| m.role } %> <% members.keys.sort{|x,y| x.position <=> y.position}.each do |role| %>

    <%= role.name %>

      <% members[role].each do |m| %>
    • <%= link_to_user m.user %> (<%= format_date m.created_at %>)
    • <% end %>
    <% end %> ================================================ FILE: app/views/projects/map.html.erb ================================================ <%= javascript_include_tag 'jit' %> <%= javascript_include_tag 'enterprise_map' %> <%= stylesheet_link_tag 'enterprise_map' %> Refresh
    <%= help_section("project_map") %>
    <% content_for :sidebar do %> <%= project_image @project %> <% end %> ================================================ FILE: app/views/projects/move.html.erb ================================================

    <%= l(:label_move_project) %>

    <% form_tag({}, :id => 'move_form') do %> <%= hidden_field_tag('id', @project.id) %>

    <%= link_to_project @project %>

    <%= select_tag "parent_id", project_tree_options_for_select(@allowed_projects, :selected => @project.parent), :onchange => remote_function(:url => { :action => 'move' }, :method => :get, :update => 'content', :with => "Form.serialize('move_form')") %>

    <%= submit_tag l(:button_move), :disable_with => l(:button_working) %> <% end %> ================================================ FILE: app/views/projects/overview.html.erb ================================================ <%= help_section("project_show") %>

    <%=l(:label_activity_latest)%>

    <%= render :partial => 'activity_streams/activity_stream_list', :locals => { :user_id => params[:user_id], :project_id => @project.id, :with_subprojects => params[:with_subprojects], :limit => 20, :max_created_at => nil } %>
    <% content_for :actionmenu do %>
    • <%= link_to l(:label_login), {:controller => :account, :action => :login} unless User.current.logged? %>
    • <% if User.current.allowed_to?(:add_subprojects, @project) %>
    • <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    • <% end %> <% if User.current.allowed_to?(:send_invitations, @project) && @project.root? %>
    • <%= link_to l(:label_invitation_new), new_project_invitation_url(@project), :class => "icon icon-users" %>
    • <% end %>
    • <%= link_to_if_authorized l(:label_motions_new), {:controller => 'motions', :action => 'new', :project_id => @project}, :class => "icon icon-motion" %>
    • <% if @project && @project.module_enabled?(:issue_tracking) %> <% end %>
    <% end %> <% content_for :sidebar do %> <% if @project.description.length > 0 %>

    <%=h @project.name %>

    <%= make_expandable ((textilizable @project.description), 80) %>
    <% end %> <% if @motions.any? %>

    <%=l(:label_motions_active)%>

    <%= render :partial => 'motions/motions', :locals =>{:motions => @motions, :max_count => 2} %>
    <% end %> <% if @news.any? && authorize_for('news', 'index') %>

    <%=l(:label_news_latest)%>

    <%= render :partial => 'news/news', :locals => {:max_count => 2} %>
    <% end %> <% if @subprojects.any? %>

    <%= l(:label_subproject_plural) %>

    <%= render :partial => 'projects/projects_list_simple', :locals => {:projects => @subprojects, :max_count => 7} %>
    <% end %> <% if @project.enterprise? %> <%= render :partial => "member_box" %> <% else %> <%= render :partial => "active_member_box_simple" %> <%= render :partial => "clearance_member_box" unless @project.is_public %> <% end %> <% if User.current.allowed_to?(:view_issues, @project) && false %>

    <%=l(:label_issue_tracking)%>

      <% for tracker in @trackers %>
    • <%= link_to l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i, :total => @total_issues_by_tracker[tracker].to_i), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1, "tracker_id" => tracker.id %>
      <%= link_to tracker.name, :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1, "tracker_id" => tracker.id %>
    • <% end %>
    <% end %> <% if @project.credits_enabled? && false%>

    <%= l(:label_dpp_scale) %><%= help_bubble :help_dpp %>

    <%= render :partial => 'point_scale', :locals => { :dpp => @project.dpp.round, :unit => unit_for(@project) } unless @project.dpp.nil? %>

    <% end %> <% end %> <% html_title(l(:label_overview)) -%> ================================================ FILE: app/views/projects/settings/_boards.html.erb ================================================ <% if @project.boards.any? %>
    <% @project.boards.each do |board| next if board.new_record? %> <% end %>
    <%= l(:label_board) %> <%= l(:field_description) %>
    <%=h board.name %> <%=h board.description %> <% if authorize_for("boards", "edit") %> <%= reorder_links('board', {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board}) %> <% end %> <% unless board.name == "General" %> <%= link_to_if_authorized l(:button_edit), {:controller => 'boards', :action => 'edit', :project_id => @project, :id => board}, :class => 'icon icon-edit' %> <%= link_to_if_authorized l(:button_delete), {:controller => 'boards', :action => 'destroy', :project_id => @project, :id => board}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> <% end %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %>

    <%= link_to_if_authorized l(:label_board_new), {:controller => 'boards', :action => 'new', :project_id => @project}, :class=>"gt-btn-gray-large"%>

    ================================================ FILE: app/views/projects/settings/_hourly_types.html.erb ================================================ <% if @project.hourly_types.any? %> <% @project.hourly_types.each do |hourly_type| next if hourly_type.new_record? %> <% end %>
    <%= l(:label_hourly_type) %> <%= l(:label_hourly_rate_per_person) %> <%= l(:label_hourly_cap) %>
    <%=h hourly_type.name %> <%=h hourly_type.hourly_rate_per_person %> <%=h hourly_type.hourly_cap %> <%= link_to l(:button_edit), {:controller => 'hourly_types', :action => 'edit', :project_id => @project, :id => hourly_type}, :class => 'icon icon-edit' %> <%= link_to l(:button_delete), {:controller => 'hourly_types', :action => 'destroy', :project_id => @project, :id => hourly_type}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %>

    <%= link_to l(:label_hourly_type_new), {:controller => 'hourly_types', :action => 'new', :project_id => @project} %>

    ================================================ FILE: app/views/projects/settings/_members.html.erb ================================================ <%= error_messages_for 'member' %> <% roles = @project.enterprise? ? Role.find_all_givable(Role::LEVEL_ENTERPRISE) : Role.find_all_givable(Role::LEVEL_PROJECT) members = @project.member_users.find(:all, :include => [:roles, :user]).sort %>
    <% if members.any? %> <% members.each do |member| %> <% next if member.new_record? %> <% end; reset_cycle %>
    <%= l(:label_user) %> / <%= l(:label_group) %> <%= l(:label_role_plural) %>
    <%= link_to_user member.user %> <%=h member.roles.sort.collect(&:to_s).join(', ') %> <% if authorize_for('members', 'edit') %> <% remote_form_for(:member, member, :url => {:controller => 'members', :action => 'edit', :id => member}, :method => :post, :html => { :id => "member-#{member.id}-roles-form", :style => 'display:none;' }) do |f| %>

    <% roles.each do |role| %>
    <% end %>

    <%= hidden_field_tag 'member[role_ids][]', '' %> <%= submit_tag l(:button_change), :disable_with => l(:button_working) %>

    <%= link_to_function l(:button_cancel), "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;" %>

    <% end %> <% end %>
    <%= link_to_function l(:button_edit), "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %> <%= link_to_remote(l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member}, :confirm => "Are you sure you want to remove #{member.user.name}?", :method => :post }, :title => l(:button_delete), :class => 'icon icon-del') if member.deletable? %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %>
    <% if User.current.allowed_to?(:send_invitations, @project) %>
    • <%= link_to l(:label_invitation_new), new_project_invitation_url(@project) %>
    <% end %>
    ================================================ FILE: app/views/projects/settings/_modules.html.erb ================================================
    <% form_for :project, @project, :url => { :action => 'modules', :id => @project }, :html => {:id => 'modules-form'} do |f| %>
    <%= l(:text_select_project_modules) %>

    <% Redmine::AccessControl.available_project_modules.each do |m| %> <% end %>



    <%= f.select(:dpp, options_for_select(Setting::PAY_SCALES, :selected => @project.dpp.nil? ? Setting::PAY_SCALES_DEFAULT : @project.dpp.round), { :include_blank => false}, { :onchange => "#{remote_function(:url => {:action => "update_scale"}, :with => "'dpp='+value")}"})%> <%= help_bubble :help_select_scale %> <%= render :partial => 'point_scale', :locals => {:unit => unit_for(@project), :dpp => @project.dpp.nil? ? Setting::PAY_SCALES_DEFAULT : @project.dpp.round } %>


    <%= check_all_links 'modules-form' %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %>
    ================================================ FILE: app/views/projects/settings/_select_new_member.html.erb ================================================
    <% users = User.active.find(:all, :limit => 100, :order => 'type, login, lastname ASC') - @project.users %> <% if roles.any? && users.any? %> <% remote_form_for(:member, @member, :url => {:controller => 'members', :action => 'new', :id => @project}, :method => :post) do |f| %>
    <%=l(:label_member_new)%>

    <%= text_field_tag 'user_search', nil, :size => "40" %>

    <%= observe_field(:user_search, :frequency => 0.5, :update => :users, :url => { :controller => 'members', :action => 'autocomplete_for_member', :id => @project }, :with => 'q') %>
    <%= users_check_box_tags 'member[user_ids][]', users %>

    <%= l(:label_role_plural) %>: <% roles.each do |role| %> <% end %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <% end %> <% end %>
    ================================================ FILE: app/views/projects/settings/_wiki.html.erb ================================================ <% remote_form_for :wiki, @wiki, :url => { :controller => 'wikis', :action => 'edit', :id => @project }, :builder => TabularFormBuilder, :lang => current_language do |f| %> <%= error_messages_for 'wiki' %>

    <%= f.text_field :start_page, :size => 60, :required => true %>
    <%= l(:text_unallowed_characters) %>: , . / ? ; : |

    <%= link_to(l(:button_delete), {:controller => 'wikis', :action => 'destroy', :id => @project}, :class => 'icon icon-del') if @wiki && !@wiki.new_record? %>
    <%= submit_tag((@wiki.nil? || @wiki.new_record?) ? l(:button_create) : l(:button_save)) %>
    <% end %> ================================================ FILE: app/views/projects/settings.html.erb ================================================ <%= render_tabs project_settings_tabs %> <% html_title(l(:label_settings)) -%> <% content_for :actionmenu do %>
    • <% if @project.active? %> <%= link_to(l(:label_archive_project), {:controller => 'projects', :action => 'archive', :id => @project}, {:confirm => l(:text_confirm_archive_project), :method => :post}) %> <% else %> <%= link_to(l(:label_unarchive_project), {:controller => 'projects', :action => 'unarchive', :id => @project}, {:confirm => l(:text_confirm_unarchive_project), :method => :post}) %> <% end %>
    • <%= link_to(l(:label_delete_project), {:controller => 'projects', :action => 'destroy', :id => @project}, {:confirm => l(:text_confirm_delete_project), :method => :post}) %>
    • <% unless @project.root? %> <% end %>
    <% end %> <% content_for :sidebar do %> <%= help_section("project_settings") %> <% end %> ================================================ FILE: app/views/projects/show.png.flexi ================================================ @project.operate do |image| image.resize '100*200' #image.shadow :background => 'white' #, :blur => 30 end ================================================ FILE: app/views/projects/team.html.erb ================================================ <%= help_section("project_team") %> <%= error_messages_for 'member' %>
    <%= render :partial => 'team_list', :locals => {:project => @project} %>
    <%= render :partial => "active_member_box" %>
    <% content_for :actionmenu do %>
      <% if User.current.allowed_to?(:send_invitations, @project) %>
    • <%= link_to l(:label_invitation_new), new_project_invitation_url(@project), :class => "icon icon-users" %>
    • <% end %>
    <% end %> <% html_title(l(:label_team)) %> ================================================ FILE: app/views/projects/team_update.js.rjs ================================================ page.replace 'team_list' , :partial => 'team_list', :locals => {:project => @project} page.replace 'team_link' , :partial => 'team_link' , :locals => {:project => @project} page.call 'add_lightbox', 'help_button_core_points' #adding hook to create button for lightbox page[User.current.login].visual_effect :highlight ================================================ FILE: app/views/queries/_columns.html.erb ================================================
    <%= select_tag 'available_columns', options_for_select((query.available_columns - query.columns).collect {|column| [column.caption, column.name]}), :multiple => true, :size => 10, :style => "width:150px" %>
    <%= select_tag 'query[column_names][]', options_for_select(query.columns.collect {|column| [column.caption, column.name]}), :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px" %>
    <% content_for :header_tags do %> <%= javascript_include_tag 'select_list_move' %> <% end %> ================================================ FILE: app/views/queries/_filters.html.erb ================================================
    <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> <% field = filter[0] options = filter[1] %> id="tr_<%= field %>" class="filter"> <% end %>
    <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %>
    <%= l(:label_filter_add) %>: <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small", :name => nil %>
    ================================================ FILE: app/views/queries/_form.html.erb ================================================ <%= error_messages_for 'query' %> <%= hidden_field_tag 'confirm', 1 %>

    <%= text_field 'query', 'name', :size => 80 %>

    <% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %>

    <%= check_box 'query', 'is_public', :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("query_is_for_all").checked = false; $("query_is_for_all").disabled = true;} else {$("query_is_for_all").disabled = false;}') %>

    <% end %>

    <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>

    <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', :onclick => 'if (this.checked) {$("#columns").hide()} else {$("#columns").show()}' %>

    <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

    <%= l(:label_filter_plural) %> <%= render :partial => 'queries/filters', :locals => {:query => query}%>
    <%= l(:label_sort) %> <% 3.times do |i| %> <%= i+1 %>: <%= select_tag("query[sort_criteria][#{i}][]", options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i))) %> <%= select_tag("query[sort_criteria][#{i}][]", options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i))) %>
    <% end %>
    <% content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> <%= l(:field_column_names) %> <%= render :partial => 'queries/columns', :locals => {:query => query}%> <% end %>
    ================================================ FILE: app/views/queries/edit.html.erb ================================================

    <%= l(:label_query) %>

    <% form_tag({:action => 'edit', :id => @query}, :onsubmit => 'selectAllOptions("selected_columns");') do %> <%= render :partial => 'form', :locals => {:query => @query} %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/queries/index.html.erb ================================================
    <%= link_to_if_authorized l(:label_query_new), {:controller => 'queries', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>

    <%= l(:label_query_plural) %>

    <% if @queries.empty? %>

    <%=l(:label_no_data)%>

    <% else %> <% @queries.each do |query| %> <% end %>
    <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %> <% if query.editable_by?(User.current) %> <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => query}, :class => 'icon icon-edit' %> <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> <% end %>
    <% end %> ================================================ FILE: app/views/queries/new.html.erb ================================================

    <%= l(:label_query_new) %>

    <% form_tag({:action => 'new', :project_id => @query.project}, :onsubmit => 'selectAllOptions("selected_columns");') do %> <%= render :partial => 'form', :locals => {:query => @query} %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/quotes/edit.html.erb ================================================

    Editing quote

    <% form_for(@quote) do |f| %> <%= f.error_messages %>

    <%= f.label :author %>
    <%= f.text_field :author %>

    <%= f.label :body %>
    <%= f.text_field :body %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @quote %> | <%= link_to 'Back', quotes_path %> ================================================ FILE: app/views/quotes/index.html.erb ================================================

    Listing quotes

    <% @quotes.each do |quote| %> <% end %>
    Author Body User
    <%=h quote.author %> <%=h quote.body %> <%=link_to_user_from_id quote.user_id %> <%= link_to 'Show', quote %> <%= link_to 'Edit', edit_quote_path(quote) %> <%= link_to 'Destroy', quote, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New quote', new_quote_path %> ================================================ FILE: app/views/quotes/new.html.erb ================================================

    New quote

    <% form_for(@quote) do |f| %> <%= f.error_messages %>

    <%= f.label :author %>
    <%= f.text_field :author %>

    <%= f.label :body %>
    <%= f.text_area :body %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', quotes_path %> ================================================ FILE: app/views/quotes/show.html.erb ================================================

    Author: <%=h @quote.author %>

    translation missing: en, field_body: <%=h @quote.body %>

    User: <%=h @quote.user_id %>

    <%= link_to 'Edit', edit_quote_path(@quote) %> | <%= link_to 'Back', quotes_path %> ================================================ FILE: app/views/reports/_details.html.erb ================================================ <% if @statuses.empty? or rows.empty? %>

    <%=l(:label_no_data)%>

    <% else %> <% col_width = 70 / (@statuses.length+3) %> <% for status in @statuses %> <% end %> <% for row in rows %> "> <% for status in @statuses %> <% end %> <% end %>
    <%= status.name %><%=l(:label_open_issues_plural)%> <%=l(:label_closed_issues_plural)%> <%=l(:label_total)%>
    <%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "status_id" => status.id }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "status_id" => status.id, "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "o" %> <%= aggregate_link data, { field_name => row.id, "closed" => 1 }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "c" %> <%= aggregate_link data, { field_name => row.id }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "*" %>
    <% end reset_cycle %> ================================================ FILE: app/views/reports/_simple.html.erb ================================================ <% if @statuses.empty? or rows.empty? %>

    <%=l(:label_no_data)%>

    <% else %> <% for row in rows %> "> <% end %>
    <%=l(:label_open_issues_plural)%> <%=l(:label_closed_issues_plural)%> <%=l(:label_total)%>
    <%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id %> <%= aggregate_link data, { field_name => row.id, "closed" => 0 }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "o" %> <%= aggregate_link data, { field_name => row.id, "closed" => 1 }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "c" %> <%= aggregate_link data, { field_name => row.id }, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), :set_filter => 1, "#{field_name}" => row.id, "status_id" => "*" %>
    <% end reset_cycle %> ================================================ FILE: app/views/reports/issue_report.html.erb ================================================

    <%=l(:label_report_plural)%>

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), :detail => 'tracker' %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), :detail => 'assigned_to' %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %>

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), :detail => 'author' %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %>
    <% if @project.children.any? %>

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), :detail => 'subproject' %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %>
    <% end %>
    ================================================ FILE: app/views/reports/issue_report_details.html.erb ================================================

    <%=l(:label_report_plural)%>

    <%=@report_title%>

    <%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %>
    <%= link_to l(:button_back), :action => 'issue_report' %> ================================================ FILE: app/views/reputations/edit.html.erb ================================================

    Editing reputation

    <% form_for(@reputation) do |f| %> <%= f.error_messages %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :reputation_type %>
    <%= f.text_field :reputation_type %>

    <%= f.label :value %>
    <%= f.text_field :value %>

    <%= f.label :params %>
    <%= f.text_field :params %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @reputation %> | <%= link_to 'Back', reputations_path %> ================================================ FILE: app/views/reputations/index.html.erb ================================================

    Listing reputations

    <% @reputations.each do |reputation| %> <% end %>
    User Workstream translation missing: en, field_reputation_type Value translation missing: en, field_params
    <%=h reputation.user_id %> <%=h reputation.project_id %> <%=h reputation.reputation_type %> <%=h reputation.value %> <%=h reputation.params %> <%= link_to 'Show', reputation %> <%= link_to 'Edit', edit_reputation_path(reputation) %> <%= link_to 'Destroy', reputation, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New reputation', new_reputation_path %> ================================================ FILE: app/views/reputations/new.html.erb ================================================

    New reputation

    <% form_for(@reputation) do |f| %> <%= f.error_messages %>

    <%= f.label :user_id %>
    <%= f.text_field :user_id %>

    <%= f.label :project_id %>
    <%= f.text_field :project_id %>

    <%= f.label :reputation_type %>
    <%= f.text_field :reputation_type %>

    <%= f.label :value %>
    <%= f.text_field :value %>

    <%= f.label :params %>
    <%= f.text_field :params %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', reputations_path %> ================================================ FILE: app/views/reputations/show.html.erb ================================================

    User: <%=h @reputation.user_id %>

    Workstream: <%=h @reputation.project_id %>

    translation missing: en, field_reputation_type: <%=h @reputation.reputation_type %>

    Value: <%=h @reputation.value %>

    translation missing: en, field_params: <%=h @reputation.params %>

    <%= link_to 'Edit', edit_reputation_path(@reputation) %> | <%= link_to 'Back', reputations_path %> ================================================ FILE: app/views/retro_ratings/edit.html.erb ================================================

    Editing retro_rating

    <% form_for(@retro_rating) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @retro_rating %> | <%= link_to 'Back', retro_ratings_path %> ================================================ FILE: app/views/retro_ratings/index.html.erb ================================================

    Listing retro_ratings

    <% @retro_ratings.each do |retro_rating| %> <% end %>
    <%= link_to 'Show', retro_rating %> <%= link_to 'Edit', edit_retro_rating_path(retro_rating) %> <%= link_to 'Destroy', retro_rating, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New retro_rating', new_retro_rating_path %> ================================================ FILE: app/views/retro_ratings/new.html.erb ================================================

    New retro_rating

    <% form_for(@retro_rating) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', retro_ratings_path %> ================================================ FILE: app/views/retro_ratings/show.html.erb ================================================ <%= link_to 'Edit', edit_retro_rating_path(@retro_rating) %> | <%= link_to 'Back', retro_ratings_path %> ================================================ FILE: app/views/retros/_issue_details.html.erb ================================================ <%= link_to_issue(issue, :css_class => '') %> - <%= issue.points.round %> credits <%= image_tag "dice_#{issue.points_from_credits}.png", :alt => "#{issue.points} credits"%> Author: <%= link_to_user_from_id(issue.author_id) %> Owner: <%= link_to_user_from_id(issue.assigned_to_id) %> <% if issue.has_team? %> Team: <%= team_from_issue issue %> <% end %> <% if issue.has_todos? %> Todos: <% issue.todos.each do |todo| %> <%= todo.subject %> <%= " - (" + link_to_user_from_id(todo.owner_id) + ")" unless todo.owner_id.nil? %>
    <% end %> <% end %> ================================================ FILE: app/views/retros/edit.html.erb ================================================

    Editing retro

    <% form_for(@retro) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @retro %> | <%= link_to 'Back', retros_path %> ================================================ FILE: app/views/retros/index.html.erb ================================================

    Listing retros

    <% @retros.each do |retro| %> <% end %>
    <%= link_to 'Show', retro %> <%= link_to 'Edit', edit_retro_path(retro) %> <%= link_to 'Destroy', retro, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New retro', new_retro_path %> ================================================ FILE: app/views/retros/new.html.erb ================================================

    New retro

    <% form_for(@retro) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', retros_path %> ================================================ FILE: app/views/retros/show.html.erb ================================================ <%= javascript_include_tag 'retro' %> <%= help_section "retrospective_show" %>

    Retrospective #<%= @retro.id %> : <%= @total_points.round %> Credits - <%= render_title_date %>

    <% if @user_retro_hash.has_key? User.current.id%> <%= javascript_tag 'var belongs = true;'%> <% end %>
    <% @user_retro_hash = @user_retro_hash.sort_by {|x| [ x[1]["total_points"] , x[1]["total_ideas"] , x[1]["total_journals"] ] }.reverse @user_retro_hash.each do |x| user_id = x[0] user_retro = x[1] %> <% end %>
    <%= avatar_from_id(user_id) %>
    <%= link_to_user_from_id(user_id) %>
    <%= user_retro["total_points"].round %> credits (<%= user_retro["percentage_points"] %>%)
    <%= user_retro["total_ideas"] %> ideas
    <%= user_retro["total_journals"] %> comments
    <%= user_retro["total_votes"] %> votes
     
    Confidence<%= help_bubble :help_confidence %>



    Contribution Breakdown : <%= format_date(@retro.from_date) %> to <%= format_date(@retro.to_date)%>

    <% @retro.issues.each do |issue| %> <%= render :partial => "issue_details", :locals => { :issue => issue } %> <% end %>

    <% content_for :sidebar do %>

    <% @chart_width = @point_totals.length*70 + 50 @chart_width = 1000 if @chart_width > 1000 %> 'x', :axis_labels => @axis_labels, :bar_width_and_spacing => '15,2,10', :legend => ['Ideas','Votes','Comments'], :custom => "chm=N*f0*,000000,0,-1,11|N*f0*,000000,1,-1,11|N*f0*,000000,2,-1,11&chds=0,#{@max_range}" # :custom => 'chma=50,50,50,50|200,200' ) %>"/>




    <% end %> ================================================ FILE: app/views/retros/show_multiple.html.erb ================================================

    Retrospective #<%= @retro.id %> : <%= @total_points %> Points (<%= format_date(@retro.from_date) %> to <%= format_date(@retro.to_date) %>)

    <% @user_retro_hash.keys.each do |user_id| user_retro = @user_retro_hash[user_id] %>

    <%= link_to_user_from_id(user_id) %>

    <% if user_retro["issues"].length > 0 %> <% end %>
    <%= user_retro["total_points"] %> points (<%= user_retro["percentage_points"] %>%)
    <%= user_retro["total_journals"] %> journals
    <%= user_retro["total_votes"] %> votes
    <% user_retro["issues"].each do |issue| %> <%= link_to_issue(issue) %> - <%= issue.points %> pts
    <% end %>

    <% end %> <% content_for :sidebar do %>

    <% @chart_width = @point_totals.length*70 + 50 @chart_width = 1000 if @chart_width > 1000 %> 'x', :axis_labels => @axis_labels, :bar_width_and_spacing => '15,2,10', :legend => ['Points','Votes','Journals'], :custom => "chm=N*f0*,000000,0,-1,11|N*f0*,000000,1,-1,11|N*f0*,000000,2,-1,11&chds=0,#{@max_range}" ) %>"/>








    <% end %> ================================================ FILE: app/views/roles/_form.html.erb ================================================ <%= error_messages_for 'role' %> <% unless @role.builtin? %>

    <%= f.text_field :name, :required => true %>

    <%= f.check_box :assignable %>

    <% if @role.new_record? && @roles.any? %>

    <%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@roles, :id, :name)) %>

    <% end %>
    <% end %>

    <%= l(:label_permissions) %>

    <% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %> <% perms_by_module.keys.sort.each do |mod| %>
    <%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %> <% perms_by_module[mod].each do |permission| %> <% end %>
    <% end %>
    <%= check_all_links 'permissions' %> <%= hidden_field_tag 'role[permissions][]', '' %>
    ================================================ FILE: app/views/roles/edit.html.erb ================================================

    <%= link_to l(:label_role_plural), :controller => 'roles', :action => 'index' %> » <%=h @role.name %>

    <% labelled_tabular_form_for :role, @role, :url => { :action => 'edit' }, :html => {:id => 'role_form'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= submit_tag l(:button_save) %>
    <% end %> ================================================ FILE: app/views/roles/list.html.erb ================================================
    <%= link_to l(:label_role_new), {:action => 'new'}, :class => 'icon icon-add' %>

    <%=l(:label_role_plural)%>

    <% for role in @roles %> "> <% end %>
    <%=l(:label_role)%> <%=l(:button_sort)%>
    <%= content_tag(role.builtin? ? 'em' : 'span', link_to(role.name, :action => 'edit', :id => role)) %> <% unless role.builtin? %> <%= reorder_links('role', {:action => 'edit', :id => role}) %> <% end %> <%= link_to(l(:button_delete), { :action => 'destroy', :id => role }, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') unless role.builtin? %>

    <%= pagination_links_full @role_pages %>

    <%= link_to l(:label_permissions_report), :action => 'report' %>

    <% html_title(l(:label_role_plural)) -%> ================================================ FILE: app/views/roles/new.html.erb ================================================

    <%= link_to l(:label_role_plural), :controller => 'roles', :action => 'index' %> » <%=l(:label_role_new)%>

    <% labelled_tabular_form_for :role, @role, :url => { :action => 'new' }, :html => {:id => 'role_form'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/roles/report.html.erb ================================================

    <%= link_to l(:label_role_plural), :controller => 'roles', :action => 'index' %> » <%=l(:label_permissions_report)%>

    <% form_tag({:action => 'report'}, :id => 'permissions_form') do %> <%= hidden_field_tag 'permissions[0]', '', :id => nil %> <% @roles.each do |role| %> <% end %> <% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %> <% perms_by_module.keys.sort.each do |mod| %> <% unless mod.blank? %> <% end %> <% perms_by_module[mod].each do |permission| %> <% @roles.each do |role| %> <% end %> <% end %> <% end %>
    <%=l(:label_permissions)%> <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %> <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.role-#{role.id}')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %>
      <%= l_or_humanize(mod, :prefix => 'project_module_') %>
    <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('.permission-#{permission.name} input')", :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> <%= l_or_humanize(permission.name, :prefix => 'permission_') %> <% if role.setable_permissions.include? permission %> <%= check_box_tag "permissions[#{role.id}][]", permission.name, (role.permissions.include? permission.name), :id => nil, :class => "role-#{role.id}" %> <% end %>

    <%= check_all_links 'permissions_form' %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/search/index.html.erb ================================================ <% @page_header_name = l(:label_search) %> <% form_tag({}, {:method => :get, :class=>"search-form"}) do %>

    <%= text_field_tag 'q', @question, :size => 60, :id => 'search-input' %> <%= javascript_tag "Field.focus('search-input')" %> <%= project_select_tag %>

    <% @object_types.each do |t| %> <% end %> <%= submit_tag l(:button_search), :name => 'submit', :class => "gt-btn-gray-large"%>

    <% end %> <% content_for :sidebar do %>
    <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %>
    <% end %> <% if @results %>

    <%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)

    <% @results.each do |e| %>
    <%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url, :class => (e.event_type.match(/^issue/)) ? "fancyframe" : "noframe" %>
    <%= highlight_tokens(e.event_description, @tokens) %> <%= format_time(e.event_datetime) %>
    <% end %>
    <% end %>

    <% if @pagination_previous_date %> <%= link_to_remote ('« ' + l(:label_previous)), {:update => :content, :url => params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S")) }, :href => url_for(params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>  <% end %> <% if @pagination_next_date %> <%= link_to_remote (l(:label_next) + ' »'), {:update => :content, :url => params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S")) }, :href => url_for(params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> <% end %>

    <% html_title(l(:label_search)) -%> ================================================ FILE: app/views/settings/_authentication.html.erb ================================================ <% form_tag({:action => 'edit', :tab => 'authentication'}) do %>

    <%= setting_check_box :login_required %>

    <%= setting_select :autologin, [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]}, :blank => :label_disabled %>

    <%= setting_select :self_registration, [[l(:label_disabled), "0"], [l(:label_registration_activation_by_email), "1"], [l(:label_registration_manual_activation), "2"], [l(:label_registration_automatic_activation), "3"]] %>

    <%= setting_text_field :password_min_length, :size => 6 %>

    <%= setting_check_box :lost_password, :label => :label_password_lost %>

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    <%= setting_check_box :rest_api_enabled %>

    <%= link_to l(:label_ldap_authentication), :controller => 'auth_sources', :action => 'list' %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/settings/_display.html.erb ================================================ <% form_tag({:action => 'edit', :tab => 'display'}) do %>

    <%= setting_select :ui_theme, Redmine::Themes.themes.collect {|t| [t.name, t.id]}, :blank => :label_default, :label => :label_theme %>

    <%= setting_select :default_language, lang_options_for_select(false) %>

    <%= setting_select :start_of_week, [[day_name(1),'1'], [day_name(7),'7']], :blank => :label_language_based %>

    <%= setting_select :date_format, Setting::DATE_FORMATS.collect {|f| [Date.today.strftime(f), f]}, :blank => :label_language_based %>

    <%= setting_select :time_format, Setting::TIME_FORMATS.collect {|f| [Time.now.strftime(f), f]}, :blank => :label_language_based %>

    <%= setting_select :user_format, @options[:user_format] %>

    <%= setting_check_box :gravatar_enabled %>

    <%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid']], :blank => :label_none %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/settings/_general.html.erb ================================================ <% form_tag({:action => 'edit'}) do %>

    <%= setting_text_field :app_title, :size => 30 %>

    <%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %>

    <%= setting_text_field :attachment_max_size, :size => 6 %> KB

    <%= setting_text_field :per_page_options, :size => 20 %>
    <%= l(:text_comma_separated) %>

    <%= setting_text_field :activity_days_default, :size => 6 %> <%= l(:label_day_plural) %>

    <%= setting_text_field :host_name, :size => 60 %>
    <%= l(:label_example) %>: <%= @guessed_host_and_path %>

    <%= setting_select :protocol, [['HTTP', 'http'], ['HTTPS', 'https']] %>

    <%= setting_select :text_formatting, Redmine::WikiFormatting.format_names.collect{|name| [name, name.to_s]}, :blank => :label_none %>

    <%= setting_select :wiki_compression, [['Gzip', 'gzip']], :blank => :label_none %>

    <%= setting_text_field :feeds_limit, :size => 6 %>

    <%= setting_text_field :file_max_size_displayed, :size => 6 %> KB

    <%= setting_text_field :diff_max_lines_displayed, :size => 6 %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <%= textile_editor_initialize(:framework => :jquery) %> <% end %> ================================================ FILE: app/views/settings/_issues.html.erb ================================================ <% form_tag({:action => 'edit', :tab => 'issues'}) do %>

    <%= setting_check_box :cross_project_issue_relations %>

    <%= setting_check_box :display_subprojects_issues %>

    <%= setting_text_field :issues_export_limit, :size => 6 %>

    <%= l(:setting_issue_list_default_columns) %> <%= setting_multiselect(:issue_list_default_columns, Query.new.available_columns.collect {|c| [c.caption, c.name.to_s]}, :label => false) %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/settings/_mail_handler.html.erb ================================================ <% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %>

    <%= setting_text_area :mail_handler_body_delimiters, :rows => 5 %>
    <%= l(:text_line_separated) %>

    <%= setting_check_box :mail_handler_api_enabled, :onclick => "if (this.checked) { Form.Element.enable('settings_mail_handler_api_key'); } else { Form.Element.disable('settings_mail_handler_api_key'); }"%>

    <%= setting_text_field :mail_handler_api_key, :size => 30, :id => 'settings_mail_handler_api_key', :disabled => !Setting.mail_handler_api_enabled? %> <%= link_to_function l(:label_generate_key), "if ($('settings_mail_handler_api_key').disabled == false) { $('settings_mail_handler_api_key').value = randomKey(20) }" %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/settings/_notifications.html.erb ================================================ <% if @deliveries %> <% form_tag({:action => 'edit', :tab => 'notifications'}) do %>

    <%= setting_text_field :mail_from, :size => 60 %>

    <%= setting_check_box :bcc_recipients %>

    <%= setting_check_box :plain_text_mail %>

    <%=l(:text_select_mail_notifications)%> <%= setting_multiselect(:notified_events, @notifiables.collect {|notifiable| [l_or_humanize(notifiable, :prefix => 'label_'), notifiable]}, :label => false) %>

    <%= check_all_links('notified_events') %>

    <%= l(:setting_emails_footer) %> <%= setting_text_area :emails_footer, :label => false, :class => 'wiki-edit', :rows => 5 %>
    <%= link_to l(:label_send_test_email), :controller => 'admin', :action => 'test_email' %>
    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> <% else %>
    <%= simple_format(l(:text_email_delivery_not_configured)) %>
    <% end %> ================================================ FILE: app/views/settings/_projects.html.erb ================================================ <% form_tag({:action => 'edit', :tab => 'projects'}) do %>

    <%= setting_check_box :default_projects_public %>

    <%= setting_multiselect(:default_projects_modules, Redmine::AccessControl.available_project_modules.collect {|m| [l_or_humanize(m, :prefix => "project_module_"), m.to_s]}) %>

    <%= setting_check_box :sequential_project_identifiers %>

    <%= setting_select :new_project_user_role_id, Role.find_all_givable(Role::LEVEL_PROJECT).collect {|r| [r.name, r.id.to_s]}, :blank => "--- #{l(:actionview_instancetag_blank_option)} ---" %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/settings/edit.html.erb ================================================

    <%= l(:label_settings) %>

    <%= render_tabs administration_settings_tabs %> <% html_title(l(:label_settings), l(:label_administration)) -%> ================================================ FILE: app/views/settings/plugin.html.erb ================================================

    <%= l(:label_settings) %>: <%=h @plugin.name %>

    <% form_tag({:action => 'plugin'}) do %>
    <%= render :partial => @partial, :locals => {:settings => @settings}%>
    <%= submit_tag l(:button_apply), :disable_with => l(:button_working) %>
    <% end %>
    ================================================ FILE: app/views/shares/edit.html.erb ================================================

    Editing share

    <% form_for(@share) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @share %> | <%= link_to 'Back', shares_path %> ================================================ FILE: app/views/shares/index.html.erb ================================================

    <%=l(:label_team) %>

    <% @shares.each do |share| %> <% end %>
    <%= link_to 'Show', share %> <%= link_to 'Edit', edit_share_path(share) %> <%= link_to 'Destroy', share, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New share', new_share_path %> ================================================ FILE: app/views/shares/new.html.erb ================================================

    New share

    <% form_for(@share) do |f| %> <%= f.error_messages %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', shares_path %> ================================================ FILE: app/views/shares/show.html.erb ================================================ <%= link_to 'Edit', edit_share_path(@share) %> | <%= link_to 'Back', shares_path %> ================================================ FILE: app/views/todos/edit.html.erb ================================================

    Editing todo

    <% form_for(@todo) do |f| %> <%= f.error_messages %>

    <%= f.label :subject %>
    <%= f.text_field :subject %>

    <%= f.label :author_id %>
    <%= f.text_field :author_id %>

    <%= f.label :owner_id %>
    <%= f.text_field :owner_id %>

    <%= f.label :completed_on %>
    <%= f.datetime_select :completed_on %>

    <%= f.submit 'Update' %>

    <% end %> <%= link_to 'Show', @todo %> | <%= link_to 'Back', todos_path %> ================================================ FILE: app/views/todos/index.html.erb ================================================

    Listing todos

    <% @todos.each do |todo| %> <% end %>
    Subject Author translation missing: en, field_owner translation missing: en, field_completed_on
    <%=h todo.subject %> <%=h todo.author_id %> <%=h todo.owner_id %> <%=h todo.completed_on %> <%= link_to 'Show', todo %> <%= link_to 'Edit', edit_todo_path(todo) %> <%= link_to 'Destroy', todo, :confirm => 'Are you sure?', :method => :delete %>

    <%= link_to 'New todo', new_todo_path %> ================================================ FILE: app/views/todos/new.html.erb ================================================

    New todo

    <% form_for(@todo) do |f| %> <%= f.error_messages %>

    <%= f.label :subject %>
    <%= f.text_field :subject %>

    <%= f.label :author_id %>
    <%= f.text_field :author_id %>

    <%= f.label :owner_id %>
    <%= f.text_field :owner_id %>

    <%= f.label :completed_on %>
    <%= f.datetime_select :completed_on %>

    <%= f.submit 'Create' %>

    <% end %> <%= link_to 'Back', todos_path %> ================================================ FILE: app/views/todos/show.html.erb ================================================

    Subject: <%=h @todo.subject %>

    Author: <%=h @todo.author_id %>

    translation missing: en, field_owner: <%=h @todo.owner_id %>

    translation missing: en, field_completed_on: <%=h @todo.completed_on %>

    <%= link_to 'Edit', edit_todo_path(@todo) %> | <%= link_to 'Back', todos_path %> ================================================ FILE: app/views/trackers/_form.html.erb ================================================ <%= error_messages_for 'tracker' %>

    <%= f.text_field :name, :required => true %>

    <% if @tracker.new_record? && @trackers.any? %>

    <%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@trackers, :id, :name)) %>

    <% end %>
    <%= submit_tag l(@tracker.new_record? ? :button_create : :button_save) %>
    <% if @projects.any? %>
    <%= l(:label_project_plural) %> <%= project_nested_ul(@projects) do |p| content_tag('label', check_box_tag('tracker[project_ids][]', p.id, @tracker.projects.include?(p), :id => nil) + ' ' + h(p)) end %> <%= hidden_field_tag('tracker[project_ids][]', '', :id => nil) %>

    <%= check_all_links 'tracker_project_ids' %>

    <% end %>
    ================================================ FILE: app/views/trackers/edit.html.erb ================================================

    <%= link_to l(:label_tracker_plural), :controller => 'trackers', :action => 'index' %> » <%=h @tracker %>

    <% form_for :tracker, @tracker, :url => { :action => 'edit' }, :builder => TabularFormBuilder do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> ================================================ FILE: app/views/trackers/list.html.erb ================================================
    <%= link_to l(:label_tracker_new), {:action => 'new'}, :class => 'icon icon-add' %>

    <%=l(:label_tracker_plural)%>

    <% for tracker in @trackers %> "> <% end %>
    <%=l(:label_tracker)%> <%=l(:button_sort)%>
    <%= link_to tracker.name, :action => 'edit', :id => tracker %> <% unless tracker.workflows.count > 0 %><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)<% end %> <%= reorder_links('tracker', {:action => 'edit', :id => tracker}) %> <%= link_to(l(:button_delete), { :action => 'destroy', :id => tracker }, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>

    <%= pagination_links_full @tracker_pages %>

    <% html_title(l(:label_tracker_plural)) -%> ================================================ FILE: app/views/trackers/new.html.erb ================================================

    <%= link_to l(:label_tracker_plural), :controller => 'trackers', :action => 'index' %> » <%=l(:label_tracker_new)%>

    <% form_for :tracker, @tracker, :url => { :action => 'new' }, :builder => TabularFormBuilder do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> ================================================ FILE: app/views/users/_activity.html.erb ================================================ <% unless @events_by_day.empty? %>

    <%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %>

    <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>

    <% @events_by_day.keys.sort.reverse.each do |day| %>

    <%= format_activity_day(day) %>

    <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
    <%= format_time(e.event_datetime, false) %> <%= content_tag('span', h(e.project), :class => 'project') %> <%= link_to format_activity_title(e.event_title), e.event_url, :class => (e.event_type.match(/^issue/)) ? "fancyframe" : "noframe" %>
    <%= format_activity_description(e.event_description) %>
    <% end -%>
    <% end -%>
    <% end %> ================================================ FILE: app/views/users/_form.html.erb ================================================ <%= error_messages_for 'user' %>

    <%= f.text_field :login, :required => true, :size => 25 %>

    <%= f.text_field :firstname, :required => true %>

    <%= f.text_field :lastname, :required => true %>

    <%= f.text_field :mail, :required => true %>

    <%= f.select :language, lang_options_for_select %>

    <% if Setting.openid? %>

    <%= f.text_field :identity_url %>

    <% end %>

    <%= f.check_box :admin, :disabled => (@user == User.current) %>

    <%=l(:label_authentication)%>

    <% unless @auth_sources.empty? %>

    <%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {Element.show('password_fields');} else {Element.hide('password_fields');}" %>

    <% end %>

    <%= password_field_tag 'password', nil, :size => 25 %>
    <%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    <%= password_field_tag 'password_confirmation', nil, :size => 25 %>

    ================================================ FILE: app/views/users/_general.html.erb ================================================ <% labelled_tabular_form_for :user, @user, :url => { :controller => 'users', :action => "edit", :tab => nil }, :html => { :class => nil } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% if @user.active? -%>

    <% end -%>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/users/_general_info.html.erb ================================================ <%= avatar @user %>
    • <%=l(:field_login)%>: <%= @user.login %>
    • <% unless @user.pref.hide_mail %>
    • <%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %>
    • <% end %>
    • <%=l(:label_registered_on)%>: <%= format_date(@user.created_at) %>
    • <% unless @user.last_login_on.nil? %>
    • <%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %>
    • <% end %>
    ================================================ FILE: app/views/users/_membership.html.erb ================================================ <% unless @memberships.empty? %>

    <%=l(:label_project_plural)%>

      <% for membership in @memberships %>
    • <%= link_to(h(membership.project.name_with_ancestors), :controller => 'projects', :action => 'show', :id => membership.project) %><%= volunteering membership.project %><%= privacy membership.project %>  <%=h membership.roles.sort.collect(&:to_s).join(', ') %>
    • <% end %>
    <% end %> ================================================ FILE: app/views/users/_memberships.html.erb ================================================ <% roles = Role.find_all_givable(Role::LEVEL_PROJECT) %> <% projects = Project.active.find(:all, :order => 'lft') %>
    <% if @user.memberships.any? %> <% @user.memberships.each do |membership| %> <% next if membership.new_record? %> <% end; reset_cycle %>
    <%= l(:label_project) %> <%= l(:label_role_plural) %>
    <%=h membership.project %> <%=h membership.roles.sort.collect(&:to_s).join(', ') %> <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user, :membership_id => membership }, :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>

    <% roles.each do |role| %>
    <% end %>

    <%= hidden_field_tag 'membership[role_ids][]', '' %>
    <%= submit_tag l(:button_change) %>

    <%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %>

    <% end %>
    <%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %> <%= link_to_remote(l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership }, :method => :post }, :class => 'icon icon-del') if membership.deletable? %>
    <% else %>

    <%= l(:label_no_data) %>

    <% end %>
    <% if projects.any? %>
    <%=l(:label_project_new)%> <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user }) do %> <%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %>

    <%= l(:label_role_plural) %>: <% roles.each do |role| %> <% end %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <% end %>
    <% end %>
    ================================================ FILE: app/views/users/_reputation.html.erb ================================================

    <%= l(:label_general) %>

    <%= render :partial => "general_info" %>
    <% unless @reputations.empty? %>

    <%= l(:label_assessment_accuracy) %> <%= help_bubble :help_assessment_accuracy %>

      <% for reputation in @reputations %>
    • <%= reputation_value(reputation.reputation_type, reputation.value) %> - <%= reputation_project(reputation) %>
    • <% end %>
    <% end %> ================================================ FILE: app/views/users/add.html.erb ================================================

    <%= link_to l(:label_user_plural), :controller => 'users', :action => 'index' %> » <%=l(:label_user_new)%>

    <% labelled_tabular_form_for :user, @user, :url => { :action => "add" }, :html => { :class => nil } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>

    <%= submit_tag l(:button_create), :disable_with => l(:button_working) %> <%= submit_tag l(:button_create_and_continue), :name => 'continue', :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/users/edit.html.erb ================================================
    <%= change_status_link(@user) %>

    <%= link_to l(:label_user_plural), :controller => 'users', :action => 'index' %> » <%=h @user.login %>

    <%= render_tabs user_settings_tabs %> <% html_title(l(:label_user), @user.login, l(:label_administration)) -%> ================================================ FILE: app/views/users/index.html.erb ================================================
    <%= link_to l(:label_user_new), {:action => 'add'}, :class => 'icon icon-add' %>

    <%=l(:label_user_plural)%>

    <% form_tag({}, :method => :get) do %>
    <%= l(:label_filter_plural) %> <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> <%= text_field_tag 'name', params[:name], :size => 30 %>
    <%= submit_tag l(:button_apply), :class => "small", :name => nil, :disable_with => l(:button_working) %>
    <% end %>   <%= sort_header_tag('login', :caption => l(:field_login)) %> <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> <%= sort_header_tag('mail', :caption => l(:field_mail)) %> <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> <%= sort_header_tag('created_at', :caption => l(:field_created_at), :default_order => 'desc') %> <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> <% for user in @users -%> <%= %w(anon active registered locked)[user.status] %>"> <% end -%>
    <%= avatar(user, :size => "14") %><%= link_to h(user.login), :action => 'edit', :id => user %> <%= h(user.firstname) %> <%= h(user.lastname) %> <%= image_tag('true.png') if user.admin? %> <%= format_time(user.created_at) %> <%= change_status_link(user) %>

    <%= pagination_links_full @user_pages, @user_count %>

    <% html_title(l(:label_user_plural)) -%> ================================================ FILE: app/views/users/show.html.erb ================================================ <% @page_header_name = @user.name %>
    <%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %>

    Recent Activity

    <%= render :partial => "activity_streams/activity_stream_list", :locals => { :user_id => @user.id, :project_id => nil, :with_subprojects => nil, :limit => nil, :max_created_at => nil } %>
    <%= render :partial => "reputation" %> <%= render :partial => "membership" %>
    <% html_title @user.name %> ================================================ FILE: app/views/watchers/_watchers.html.erb ================================================

    <%= l(:label_issue_watchers) %>

    <%= watchers_list(watched) %> <% unless @watcher.nil? %> <% remote_form_for(:watcher, @watcher, :url => {:controller => 'watchers', :action => 'new', :object_type => watched.class.name.underscore, :object_id => watched}, :method => :post, :html => {:id => 'new-watcher-form'}) do |f| %>

    <%= f.select :user_id, (watched.addable_watcher_users.collect {|m| [m.name, m.id]}), :prompt => true %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>

    <%= toggle_link l(:button_cancel), 'new-watcher-form'%>

    <% end %> <% end %> <% content_for :sidebar do %>
    • <%= link_to_remote l(:button_add), :url => {:controller => 'watchers', :action => 'new', :object_type => watched.class.name.underscore, :object_id => watched} if User.current.allowed_to?(:add_issue_watchers, @project) %>
    <% end %> ================================================ FILE: app/views/welcome/_list_workstreams.html.erb ================================================ <% if workstreams.any? %>

    <%=l(:label_enterprise_latest)%>

    <% for project in workstreams %> <% end %>

    <%= link_to h(project.name), :controller => 'projects', :action => 'show', :id => project %>

    <%= textilizable project.short_description, :project => project %>

    <%= project.activity_line_show(30) %>
     
    Created: <%= since_tag(project.created_at) %> ago
    Team: <%= project.all_members.count %> members
    <%= link_to l(:label_browse_workstreams), {:controller => :projects, :action => :index}, :class => "gt-btn-blue-large" %>
    <% end %> ================================================ FILE: app/views/welcome/index.html.erb ================================================ <% content_for :mainmenu do %> <%= render :partial => 'common/main_menu_home', :locals => {:active_page => :mypage} %> <% end %> <%= help_section("welcome_index",true) %> <% if Notification.unresponded? %>
    <%=link_to l(:label_waiting_for_you, :notifications => pluralize(Notification.unresponded_count,l(:label_new_notification))) , {:controller => 'notifications', :action => 'index'} %>
    <% end %>
    <% if @my_projects.length == 0 %>

    <%= l(:help_no_workstreams) %>

    <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'})%>

    <%= link_to l(:label_browse_workstreams), {:controller => :projects, :action => :index}%>

    <% else %>

    <%=l(:label_activities_i_belong_to)%>

    <%= render :partial => 'activity_streams/activity_stream_list', :locals => { :user_id => User.current.id, :project_id => nil, :with_subprojects => "custom", :limit => 30, :max_created_at => nil } %>
    <% end %>
    <% if @my_projects.length > 0 %>

    <%= l(:label_projects_recent) %> <%= link_to l(:label_view_all), {:controller => "my", :action => "projects"}, :class => "floating-h-link" %>

    <%= render :partial => 'projects/projects_list_simple', :locals => {:projects => @my_projects, :max_count => 10} %>
    <% end %> <%= render :partial => "my/blocks/news", :locals => {:news => @news, :maxcount => 2 } %> <% if @assigned_issues.length > 0 %>

    <%= l(:label_my_issues) %><%= link_to l(:label_view_all), {:controller => "my", :action => "issues"}, :class => "floating-h-link" %>

    <%= render :partial => "issues/list_very_simple", :locals => {:issues => @assigned_issues} %> <% end %>
    <% content_for :actionmenu do %>
    • <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => "icon icon-add" %>
    • <%= link_to l(:label_browse_workstreams), {:controller => 'projects', :action => 'index'}, :class => "icon icon-projects" %>
    <% end %> ================================================ FILE: app/views/welcome/robots.html.erb ================================================ User-agent: * Disallow: /boards Disallow: /issues Disallow: /activity Disallow: /projects Disallow: /news <% @projects.each do |p| -%> Disallow: /projects/<%= p.to_param %>/issues Disallow: /projects/<%= p.to_param %>/activity Disallow: /projects/<%= p.to_param %> Disallow: /projects/<%= p.identifier %>/issues Disallow: /projects/<%= p.identifier %>/activity Disallow: /projects/<%= p.identifier %> <% end -%> ================================================ FILE: app/views/wiki/_content.html.erb ================================================
    <%= textilizable content, :text, :attachments => content.page.attachments %>
    ================================================ FILE: app/views/wiki/_sidebar.html.erb ================================================

    <%= l(:label_wiki) %>

    • <%= l(:label_new_page) %>
    • <%= link_to l(:field_start_page), {:action => 'index', :page => nil} %>
    • <%= link_to l(:label_index_by_title), {:action => 'special', :page => 'Page_index'} %>
    • <%= link_to l(:label_index_by_date), {:action => 'special', :page => 'Date_index'} %>
    ================================================ FILE: app/views/wiki/annotate.html.erb ================================================

    <%= @page.pretty_title %>

    <%= l(:label_version) %> <%= link_to @annotate.content.version, :action => 'index', :page => @page.title, :version => @annotate.content.version %> (<%= @annotate.content.author ? @annotate.content.author.name : "anonyme" %>, <%= format_time(@annotate.content.updated_at) %>)

    <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %> <% line_num = 1 %> <% @annotate.lines.each do |line| -%> <% line_num += 1 %> <% end -%>
    <%= line_num %> <%= link_to line[0], :controller => 'wiki', :action => 'index', :id => @project, :page => @page.title, :version => line[0] %> <%= h(line[1]) %>
    <%=h line[2] %>
    <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% content_for :sidebar do %>
    • <%= link_to(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit') %>
    • <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
    <% end %> ================================================ FILE: app/views/wiki/destroy.html.erb ================================================

    <%=h @page.pretty_title %>

    <% form_tag({}) do %>

    <%= l(:text_wiki_page_destroy_question, :descendants => @descendants_count) %>


    <% if @reassignable_to.any? %>
    : <%= select_tag 'reassign_to_id', wiki_page_options_for_select(@reassignable_to), :onclick => "$('todo_reassign').checked = true;" %> <% end %>

    <%= submit_tag l(:button_apply), :disable_with => l(:button_working) %>
    <%= link_to l(:button_cancel), :controller => 'wiki', :action => 'index', :id => @project, :page => @page.title %> <% end %> ================================================ FILE: app/views/wiki/diff.html.erb ================================================

    <%= @page.pretty_title %>

    <%= l(:label_version) %> <%= link_to @diff.content_from.version, :action => 'index', :page => @page.title, :version => @diff.content_from.version %> (<%= @diff.content_from.author ? @diff.content_from.author.name : "anonyme" %>, <%= format_time(@diff.content_from.updated_at) %>) → <%= l(:label_version) %> <%= link_to @diff.content_to.version, :action => 'index', :page => @page.title, :version => @diff.content_to.version %>/<%= @page.content.version %> (<%= @diff.content_to.author ? @diff.content_to.author.name : "anonyme" %>, <%= format_time(@diff.content_to.updated_at) %>)


    <% content_for :sidebar do %>
    • <%= link_to(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
    <% end %> <%= html_diff(@diff) %> ================================================ FILE: app/views/wiki/edit.html.erb ================================================

    <%=h @page.pretty_title %>

    <% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %> <%= f.hidden_field :version %> <%= error_messages_for 'content' %>

    <%= f.textile_editor :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit), "autocomplete-mentions-projectid" => @project.id %>


    <%= f.text_field :comments, :size => 120, "autocomplete-mentions-projectid" => @project.id %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %>

    <%= link_to_remote l(:label_preview), { :url => { :controller => 'wiki', :action => 'preview', :id => @project, :page => @page.title }, :method => 'post', :update => 'preview', :with => "$('#wiki_form').serialize()", :complete => "$('body').scrollTo('#preview')" }, :accesskey => accesskey(:preview) %>

    <%= textile_editor_initialize(:framework => :jquery) %> <% end %>
    <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% html_title @page.pretty_title %> ================================================ FILE: app/views/wiki/export.html.erb ================================================ <%=h @page.pretty_title %> <%= textilizable @content, :text, :wiki_links => :local %> ================================================ FILE: app/views/wiki/export_multiple.html.erb ================================================ <%=h @wiki.project.name %> <%= l(:label_index_by_title) %> <% @pages.each do |page| %>
    <%= textilizable page.content ,:text, :wiki_links => :anchor %> <% end %> ================================================ FILE: app/views/wiki/history.html.erb ================================================

    <%= @page.pretty_title %>

    <%= l(:label_history) %>

    <% form_tag({:action => "diff"}, :method => :get) do %> <% show_diff = @versions.size > 1 %> <% line_num = 1 %> <% @versions.each do |ver| %> "> <% line_num += 1 %> <% end %>
    # <%= l(:field_updated_at) %> <%= l(:field_author) %> <%= l(:field_comments) %>
    <%= link_to ver.version, :action => 'index', :page => @page.title, :version => ver.version %> <%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %> <%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true || $('version_from').value > #{ver.version}) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %> <%= format_time(ver.updated_at) %> <%= ver.author ? ver.author.name : "anonyme" %> <%=h ver.comments %> <%= link_to l(:button_annotate), :action => 'annotate', :page => @page.title, :version => ver.version %>
    <%= submit_tag l(:label_view_diff), :class => 'small', :disable_with => l(:button_working) if show_diff %>
    <%= pagination_links_full @version_pages, @version_count, :page_param => :p %> <% end %> ================================================ FILE: app/views/wiki/rename.html.erb ================================================

    <%= l(:button_rename) %>: <%= @original_title %>

    <%= error_messages_for 'page' %> <% labelled_tabular_form_for :wiki_page, @page, :url => { :action => 'rename' } do |f| %>

    <%= f.text_field :title, :required => true, :size => 100 %>

    <%= f.check_box :redirect_existing_links %>

    <%= f.text_field :parent_title, :size => 100 %>

    <%= submit_tag l(:button_rename), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/wiki/show.html.erb ================================================ <%= help_section "project_wiki" %> <%= breadcrumb(@page.ancestors.reverse.collect {|parent| link_to h(parent.pretty_title), {:page => parent.title}}) %> <% if @content.version != @page.content.version %>

    <%= link_to(('« ' + l(:label_previous)), :action => 'index', :page => @page.title, :version => (@content.version - 1)) + " - " if @content.version > 1 %> <%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %> <%= '(' + link_to('diff', :controller => 'wiki', :action => 'diff', :page => @page.title, :version => @content.version) + ')' if @content.version > 1 %> - <%= link_to((l(:label_next) + ' »'), :action => 'index', :page => @page.title, :version => (@content.version + 1)) + " - " if @content.version < @page.content.version %> <%= link_to(l(:label_current_version), :action => 'index', :page => @page.title) %>
    <%= @content.author ? @content.author.name : "anon" %>, <%= format_time(@content.updated_at) %>
    <%=h @content.comments %>


    <% end %> <%= render(:partial => "wiki/content", :locals => {:content => @content}) %> <% if @editable && authorize_for('wiki', 'add_attachment') %>

    <% form_tag({ :controller => 'wiki', :action => 'add_attachment', :page => @page.title }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %>

    <%= render :partial => 'attachments/form' %>

    <%= submit_tag l(:button_add), :disable_with => l(:button_working) %>
    <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_attachment_form'); Element.show('attach_files_link'); return false;" %> <% end %>
    <% end %>

    <% other_formats_links do |f| %> <%= f.link_to 'HTML', :url => {:page => @page.title, :version => @content.version} %> <%= f.link_to 'TXT', :url => {:page => @page.title, :version => @content.version} %> <% end %>

    <% content_for :header_tags do %> <%= stylesheet_link_tag 'scm' %> <% end %> <% content_for :sidebar do %>
      <% if @editable %>
    • <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :page => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.version == @page.content.version %>
    • <%= watcher_tag(@page, User.current) %>
    • <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :page => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
    • <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :page => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
    • <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :page => @page.title}, :class => 'icon icon-move') if @content.version == @page.content.version %>
    • <%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :page => @page.title}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
    • <%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :page => @page.title, :version => @content.version }, :class => 'icon icon-cancel') if @content.version < @page.content.version %>
    • <% end %>
    • <%= link_to_if_authorized(l(:label_history), {:action => 'history', :page => @page.title}, :class => 'icon icon-history') %>
    <%= render :partial => 'sidebar' %> <% end %> <% html_title @page.pretty_title %> ================================================ FILE: app/views/wiki/special_date_index.html.erb ================================================
    <%= watcher_tag(@wiki, User.current) %>

    <%= l(:label_index_by_date) %>

    <% if @pages.empty? %>

    <%= l(:label_no_data) %>

    <% end %> <% @pages_by_date.keys.sort.reverse.each do |date| %>

    <%= format_date(date) %>

      <% @pages_by_date[date].each do |page| %>
    • <%= link_to page.pretty_title, :action => 'index', :page => page.title %>
    • <% end %>
    <% end %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> <% end %> <% unless @pages.empty? %> <% other_formats_links do |f| %> <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %> <% end %> <% end %> <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> <% end %> ================================================ FILE: app/views/wiki/special_page_index.html.erb ================================================
    <%= watcher_tag(@wiki, User.current) %>

    <%= l(:label_index_by_title) %>

    <% if @pages.empty? %>

    <%= l(:label_no_data) %>

    <% end %> <%= render_page_hierarchy(@pages_by_parent_id) %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> <% end %> <% unless @pages.empty? %> <% other_formats_links do |f| %> <%= f.link_to 'HTML', :url => {:action => 'special', :page => 'export'} %> <% end %> <% end %> <% content_for :header_tags do %> <% end %> ================================================ FILE: app/views/wikis/destroy.html.erb ================================================

    <%=l(:label_confirmation)%>

    <%= @project.name %>
    <%=l(:text_wiki_destroy_confirmation)%>

    <% form_tag({:controller => 'wikis', :action => 'destroy', :id => @project}) do %> <%= hidden_field_tag "confirm", 1 %>
    <%= submit_tag l(:button_delete), :disable_with => l(:button_working) %>
    <% end %>
    ================================================ FILE: app/views/workflows/_action_menu.html.erb ================================================
    <%= link_to l(:button_edit), {:action => 'edit'}, :class => 'icon icon-edit' %> <%= link_to l(:button_copy), {:action => 'copy'}, :class => 'icon icon-copy' %> <%= link_to l(:field_summary), {:action => 'index'}, :class => 'icon icon-summary' %>
    ================================================ FILE: app/views/workflows/copy.html.erb ================================================ <%= render :partial => 'action_menu' %>

    <%=l(:label_workflow)%>

    <% form_tag({}, :id => 'workflow_copy_form') do %>

    <%= l(:label_tracker) %>
    <%= select_tag('source_tracker_id', "" + "" + options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %>
    <%= l(:label_role) %>
    <%= select_tag('source_role_id', "" + "" + options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %>

    <%= l(:label_tracker) %>
    <%= select_tag 'target_tracker_ids', "" + options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %>
    <%= l(:label_role) %>
    <%= select_tag 'target_role_ids', "" + options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %>

    <%= submit_tag l(:button_copy), :disable_with => l(:button_working) %>
    <% end %> ================================================ FILE: app/views/workflows/edit.html.erb ================================================ <%= render :partial => 'action_menu' %>

    <%=l(:label_workflow)%>

    <%=l(:text_workflow_edit)%>:

    <% form_tag({}, :method => 'get') do %>

    <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %> <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %> <%= hidden_field_tag 'used_statuses_only', '0' %>

    <%= submit_tag l(:button_edit), :name => nil %>

    <% end %> <% if @tracker && @role && @statuses.any? %> <% form_tag({}, :id => 'workflow_form' ) do %> <%= hidden_field_tag 'tracker_id', @tracker.id %> <%= hidden_field_tag 'role_id', @role.id %> <% for new_status in @statuses %> <% end %> <% for old_status in @statuses %> "> <% new_status_ids_allowed = old_status.find_new_statuses_allowed_to([@role], @tracker).collect(&:id) -%> <% for new_status in @statuses -%> <% end -%> <% end %>
    <%=l(:label_current_status)%> <%=l(:label_new_statuses_allowed)%>
    <%= new_status.name %>
    <%= old_status.name %> <%= check_box_tag "issue_status[#{ old_status.id }][]", new_status.id, new_status_ids_allowed.include?(new_status.id) %>

    <%= check_all_links 'workflow_form' %>

    <%= submit_tag l(:button_save), :disable_with => l(:button_working) %> <% end %> <% end %> <% html_title(l(:label_workflow)) -%> ================================================ FILE: app/views/workflows/index.html.erb ================================================ <%= render :partial => 'action_menu' %>

    <%=l(:label_workflow)%>

    <% if @workflow_counts.empty? %>

    <%= l(:label_no_data) %>

    <% else %> <% @workflow_counts.first.last.each do |role, count| %> <% end %> <% @workflow_counts.each do |tracker, roles| -%> <% roles.each do |role, count| -%> <% end -%> <% end -%>
    <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %>
    <%= h tracker %> <%= link_to((count > 1 ? count : image_tag('false.png')), {:action => 'edit', :role_id => role, :tracker_id => tracker}, :title => l(:button_edit)) %>
    <% end %> ================================================ FILE: config/additional_environment.rb.example ================================================ # Copy this file to additional_environment.rb and add any statements # that need to be passed to the Rails::Initializer. `config` is # available in this context. # # Example: # # config.log_level = :debug # config.gem "example_plugin", :lib => false # config.gem "timesheet_plugin", :lib => false, :version => '0.5.0' # config.gem "aws-s3", :lib => "aws/s3" # ... # ================================================ FILE: config/boot.rb ================================================ # Don't change this file! # Configure your app in config/environment.rb and config/environments/*.rb require 'thread' RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) module Rails class << self def boot! unless booted? preinitialize pick_boot.run end end def booted? defined? Rails::Initializer end def pick_boot (vendor_rails? ? VendorBoot : GemBoot).new end def vendor_rails? File.exist?("#{RAILS_ROOT}/vendor/rails") end def preinitialize load(preinitializer_path) if File.exist?(preinitializer_path) end def preinitializer_path "#{RAILS_ROOT}/config/preinitializer.rb" end end class Boot def run load_initializer Rails::Initializer.class_eval do def load_gems @bundler_loaded ||= Bundler.require :default, Rails.env end end Rails::Initializer.run(:set_load_path) end end class VendorBoot < Boot def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) Rails::GemDependency.add_frozen_gem_path end end class GemBoot < Boot def load_initializer self.class.load_rubygems load_rails_gem require 'initializer' end def load_rails_gem if version = self.class.gem_version gem 'rails', version else gem 'rails' end rescue Gem::LoadError => load_error $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) exit 1 end class << self def rubygems_version Gem::RubyGemsVersion rescue nil end def gem_version if defined? RAILS_GEM_VERSION RAILS_GEM_VERSION elsif ENV.include?('RAILS_GEM_VERSION') ENV['RAILS_GEM_VERSION'] else parse_gem_version(read_environment_rb) end end def load_rubygems min_version = '1.3.2' require 'rubygems' unless rubygems_version >= min_version $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end def parse_gem_version(text) $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ end private def read_environment_rb File.read("#{RAILS_ROOT}/config/environment.rb") end end end end # All that for this: Rails.boot! ================================================ FILE: config/database.yml.example ================================================ production: adapter: mysql2 database: bettermeans_production host: localhost username: root password: tree encoding: utf8 development: adapter: postgresql database: bettermeans_development host: localhost min_messages: WARNING username: postgres password: tree test: &TEST adapter: postgresql database: bettermeans_test host: localhost min_messages: WARNING username: postgres password: tree test_pgsql: adapter: postgresql database: bettermeans_test host: localhost min_messages: WARNING username: postgres password: "postgres" test_sqlite3: adapter: sqlite3 dbfile: db/test.db demo: adapter: sqlite3 dbfile: db/demo.db cucumber: <<: *TEST selenium: <<: *TEST ================================================ FILE: config/email.yml.example ================================================ # Outgoing email settings production: delivery_method: :smtp smtp_settings: address: smtp.example.net port: 25 domain: example.net authentication: :login user_name: "redmine@example.net" password: "redmine" development: delivery_method: :smtp smtp_settings: address: 127.0.0.1 port: 25 domain: example.net authentication: :login user_name: "redmine@example.net" password: "redmine" ================================================ FILE: config/environment.rb ================================================ # Be sure to restart your web server when you modify this file. # Uncomment below to force Rails into production mode when # you don't control web/app server and can't set it the proper way # ENV['RAILS_ENV'] ||= 'production' #For ruby debug SCRIPT_LINES__ = {} if ENV['RAILS_ENV'] == 'development' # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') # Load Engine plugin if available begin require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot') rescue LoadError # Not available end Rails::Initializer.run do |config| # Settings in config/environments/* take precedence those specified here # Skip frameworks you're not going to use # config.frameworks -= [ :action_web_service, :action_mailer ] # Add additional load paths for sweepers config.autoload_paths += %W( #{RAILS_ROOT}/app/sweepers ) # Force all environments to use the same logger level # (by default production uses :info, the others :debug) # config.log_level = :debug # Enable page/fragment caching by setting a file-based store # (remember to create the caching directory and make it readable to the application) # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer # Make Active Record use UTC-base instead of local time # config.active_record.default_timezone = :utc # Use Active Record's schema dumper instead of SQL when creating the test database # (enables use of different database adapters for development and test environments) # config.active_record.schema_format = :ruby # Deliveries are disabled by default. Do NOT modify this section. # Define your email configuration in email.yml instead. # It will automatically turn deliveries on # config.action_mailer.perform_deliveries = false #Added this to bypass error config.action_controller.session = { :key => "_bettermeans_session", :secret => "95fd75499b43ada8cfbc538558d74312asdf" } # config.gem "rpx_now" config.after_initialize do # so rake gems:install works RPXNow.api_key = ENV['RPXNOW_KEY'] end # Load any local configuration that is kept out of source control # (e.g. gems, patches). if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb')) instance_eval File.read(File.join(File.dirname(__FILE__), 'additional_environment.rb')) end end ================================================ FILE: config/environments/cucumber.rb ================================================ # IMPORTANT: This file was generated by Cucumber 0.4.3 # Edit at your own peril - it's recommended to regenerate this file # in the future when you upgrade to a newer version of Cucumber. config.cache_classes = true # This must be true for Cucumber to operate correctly! # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test config.gem 'cucumber', :lib => false, :version => '>=0.4.3' unless File.directory?(File.join(Rails.root, 'vendor/plugins/cucumber')) config.gem 'webrat', :lib => false, :version => '>=0.5.3' unless File.directory?(File.join(Rails.root, 'vendor/plugins/webrat')) config.gem 'rspec', :lib => false, :version => '>=1.2.9' unless File.directory?(File.join(Rails.root, 'vendor/plugins/rspec')) config.gem 'rspec-rails', :lib => false, :version => '>=1.2.9' unless File.directory?(File.join(Rails.root, 'vendor/plugins/rspec-rails')) ================================================ FILE: config/environments/demo.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The production environment is meant for finished, "live" apps. # Code is not reloaded between requests config.cache_classes = true # Use a different logger for distributed setups # config.logger = SyslogLogger.new config.log_level = :info # Full error reports are disabled and caching is turned on config.action_controller.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable serving of images, stylesheets, and javascripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" # Disable mail delivery config.action_mailer.perform_deliveries = false config.action_mailer.raise_delivery_errors = false ================================================ FILE: config/environments/development.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. config.cache_classes = false # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = false config.action_view.debug_rjs = true # config.gem "rails-footnotes", :source => "http://gemcutter.org" ================================================ FILE: config/environments/production.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The production environment is meant for finished, "live" apps. # Code is not reloaded between requests config.cache_classes = true # Use a different logger for distributed setups # config.logger = SyslogLogger.new # Full error reports are disabled and caching is turned on config.action_controller.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable serving of images, stylesheets, and javascripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" # Disable delivery errors if you bad email addresses should just be ignored config.action_mailer.raise_delivery_errors = false ActionMailer::Base.smtp_settings = { :address => "smtp.sendgrid.net", :port => "25", :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :password => ENV['SENDGRID_PASSWORD'], :domain => ENV['SENDGRID_DOMAIN'], } # No email in production log config.action_mailer.logger = nil ================================================ FILE: config/environments/selenium.rb ================================================ # IMPORTANT: This file was generated by Cucumber 0.4.3 # Edit at your own peril - it's recommended to regenerate this file # in the future when you upgrade to a newer version of Cucumber. config.cache_classes = true # This must be true for Cucumber to operate correctly! # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false # Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test ================================================ FILE: config/environments/test.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = false # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false config.action_mailer.perform_deliveries = true config.action_mailer.delivery_method = :test config.action_controller.session = { :session_key => "_test_session", :secret => "some secret phrase for the tests." } # Skip protect_from_forgery in requests http://m.onkey.org/2007/9/28/csrf-protection-for-your-existing-rails-application config.action_controller.allow_forgery_protection = false ================================================ FILE: config/environments/test_pgsql.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false config.action_mailer.perform_deliveries = true config.action_mailer.delivery_method = :test config.action_controller.session = { :session_key => "_test_session", :secret => "some secret phrase for the tests." } # Skip protect_from_forgery in requests http://m.onkey.org/2007/9/28/csrf-protection-for-your-existing-rails-application config.action_controller.allow_forgery_protection = false ================================================ FILE: config/environments/test_sqlite3.rb ================================================ # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Log error messages when you accidentally call methods on nil. config.whiny_nils = true # Show full error reports and disable caching config.action_controller.consider_all_requests_local = true config.action_controller.perform_caching = false config.action_mailer.perform_deliveries = true config.action_mailer.delivery_method = :test config.action_controller.session = { :session_key => "_test_session", :secret => "some secret phrase for the tests." } # Skip protect_from_forgery in requests http://m.onkey.org/2007/9/28/csrf-protection-for-your-existing-rails-application config.action_controller.allow_forgery_protection = false ================================================ FILE: config/exceptional.yml ================================================ # here are the settings that are common to all environments common: &default_settings # You must specify your Exceptional API key here. # api-key: 6e2a223d45e17bdeac6914c20665b26524ad6344 api-key: ac8e10d6f6c96ae1dee0d6e6999bcfd1c86bddd9 # Exceptional creates a separate log file from your application's logs # available levels are debug, info, warn, error, fatal log-level: info # The exceptional agent sends data via regular http by default # Setting this value to true will send data over SSL, increasing security # There will be an additional CPU overhead in encrypting the data, however # as long as your deployment environment is not Passenger (mod_rails), this # happens in the background so as not to incur a page wait for your users. ssl: false development: <<: *default_settings # Normally no reason to collect exceptions in development # NOTE: for trial purposes you may want to enable exceptional in development enabled: false test: <<: *default_settings # No reason to collect exceptions when running tests by default enabled: false production: <<: *default_settings enabled: true staging: # It's common development practice to have a staging environment that closely # mirrors production, by default catch errors in this environment too. <<: *default_settings enabled: true cucumber: <<: *default_settings # No reason to collect exceptions when running cucmber tests by default enabled: false selenium: <<: *default_settings # No reason to collect exceptions when running cucmber tests by default enabled: false ================================================ FILE: config/initializers/10-patches.rb ================================================ require 'active_record' module ActiveRecord class Base include Redmine::I18n # Translate attribute names for validation errors display def self.human_attribute_name(attr) l("field_#{attr.to_s.gsub(/_id$/, '')}") end end end module ActiveRecord class Errors def full_messages(options = {}) full_messages = [] @errors.each_key do |attr| @errors[attr].each do |message| next unless message if attr == "base" full_messages << message else attr_name = @base.class.human_attribute_name(attr) full_messages << attr_name + ' ' + message.to_s end end end full_messages end end end module ActionView module Helpers module DateHelper # distance_of_time_in_words breaks when difference is greater than 30 years def distance_of_date_in_words(from_date, to_date = 0, options = {}) from_date = from_date.to_date if from_date.respond_to?(:to_date) to_date = to_date.to_date if to_date.respond_to?(:to_date) distance_in_days = (to_date - from_date).abs I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| case distance_in_days when 0..60 then locale.t :x_days, :count => distance_in_days.round when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round else locale.t :over_x_years, :count => (distance_in_days / 365).floor end end end end end end ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } # Adds :async_smtp and :async_sendmail delivery methods # to perform email deliveries asynchronously module AsynchronousMailer %w(smtp sendmail).each do |type| define_method("perform_delivery_async_#{type}") do |mail| Thread.start do send "perform_delivery_#{type}", mail end end end end ActionMailer::Base.send :include, AsynchronousMailer ================================================ FILE: config/initializers/20-mime_types.rb ================================================ # Add new mime types for use in respond_to blocks: Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV) Mime::Type.register 'application/pdf', :pdf Mime::Type.register 'image/png', :png ================================================ FILE: config/initializers/30-redmine.rb ================================================ I18n.default_locale = 'en' require 'redmine' ================================================ FILE: config/initializers/40-email.rb ================================================ # Loads action_mailer settings from email.yml # and turns deliveries on if configuration file is found filename = File.join(File.dirname(__FILE__), '..', 'email.yml') if File.file?(filename) mailconfig = YAML::load_file(filename) if mailconfig.is_a?(Hash) && mailconfig.has_key?(Rails.env) # Enable deliveries ActionMailer::Base.perform_deliveries = true mailconfig[Rails.env].each do |k, v| v.symbolize_keys! if v.respond_to?(:symbolize_keys!) ActionMailer::Base.send("#{k}=", v) end end end ================================================ FILE: config/initializers/activity_streams.rb ================================================ require 'activity_streams' #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # ActivityStreams configuration/initilization # NOTE: The activites keys must be unique ACTIVITY_STREAM_ACTIVITIES = { :issues => 'Issues', :news => 'News', :documents => 'Documents', :wikis => 'Wikis', :messages => 'Discussion', :workstreams => 'Workstreams', :download => 'Download a torrent' } # NOTE: These have hard coded meanings ACTIVITY_STREAM_LOCATIONS = { :public_location => 'Public Portion of of this site', :logged_in_location => 'Logged In Portion of this site', :feed_location => 'Your Activity Stream Atom Feed' } ACTIVITY_STREAM_SERVICE_STRING="MyServiceName" ACTIVITY_STREAM_USER_MODEL='User' ACTIVITY_STREAM_USER_MODEL_ID='user_id' ACTIVITY_STREAM_USER_MODEL_NAME='name' ================================================ FILE: config/initializers/admin_data.rb ================================================ AdminData::Config.set = { :is_allowed_to_view => lambda {|controller| controller.send('data_admin_logged_in?') }, :is_allowed_to_update => lambda {|controller| controller.send('data_admin_logged_in?') }, } ================================================ FILE: config/initializers/aws_s3.rb ================================================ AWS::S3::Base.establish_connection!( # :access_key_id => ENV['s3_access_key_id'], # :secret_access_key => ENV['s3_secret_access_key'] # :access_key_id => 'AKIAI7GAIRZ5YAWMYD2A', :secret_access_key => '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' ) ================================================ FILE: config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: config/initializers/bigdecimal-segfault-fix.rb ================================================ # Copyright (c) 2009 Michael Koziarski # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. require 'bigdecimal' alias BigDecimalUnsafe BigDecimal # This fixes CVE-2009-1904 however it removes legitimate functionality that your # application may depend on. You are *strongly* advised to upgrade your ruby # rather than relying on this fix for an extended period of time. def BigDecimal(initial, digits=0) if initial.size > 255 || initial =~ /e/i raise "Invalid big Decimal Value" end BigDecimalUnsafe(initial, digits) end ================================================ FILE: config/initializers/hash.rb ================================================ class Hash def +(hash2) hash2.each do |key, value| if self.has_key? key self[key] += value else self[key] = value end end end def to_array_conditions new_conditions = [] new_conditions[0] = self.map {|k,v| v.is_a?(Array) ? "#{k} in (?)" : "#{k} = ?"}.join(" AND ") self.values.each do |v| v.is_a?(Array) ? new_conditions.push(v.flatten) : new_conditions.push("#{v}") end new_conditions end end ================================================ FILE: config/initializers/inflections.rb ================================================ # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end ================================================ FILE: config/initializers/recurly_config.rb ================================================ require 'recurly' Recurly.configure do |c| # c.username = 'recurly-api@bettermeans.com' # c.password = 'd893b5c0b61142f9af4729bc6e831e7d' # c.site = 'https://bettermeans-test.recurly.com' c.username = ENV['RECURLY_USERNAME'] c.password = ENV['RECURLY_PASSWORD'] c.site = 'https://bettermeans.recurly.com' end ================================================ FILE: config/initializers/timeout.rb ================================================ Rack::Timeout.timeout = 30 # seconds ================================================ FILE: config/locales/en.yml ================================================ en: date: formats: # Use the strftime parameters for formats. # When no format has been given, it uses default. # You can provide other formats here if you like! default: "%m/%d/%Y" short: "%b %d" long: "%B %d, %Y" day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] # Don't forget the nil at the beginning; there's no such thing as a 0th month month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] # Used in date_select and datime_select. order: - :year - :month - :day time: formats: default: "%m/%d/%Y %I:%M %p" time: "%I:%M %p" short: "%d %b %H:%M" long: "%B %d, %Y %H:%M" am: "am" pm: "pm" datetime: distance_in_words: half_a_minute: "half a minute" less_than_x_seconds: one: "less than 1 second" other: "less than #{{count}} seconds" x_seconds: one: "1 second" other: "#{{count}} seconds" less_than_x_minutes: one: "less than a minute" other: "less than #{{count}} minutes" x_minutes: one: "1 minute" other: "#{{count}} minutes" about_x_hours: one: "about 1 hour" other: "about #{{count}} hours" x_days: one: "1 day" other: "#{{count}} days" about_x_months: one: "about 1 month" other: "about #{{count}} months" x_months: one: "1 month" other: "#{{count}} months" about_x_years: one: "about 1 year" other: "about #{{count}} years" over_x_years: one: "over 1 year" other: "over #{{count}} years" number: human: format: delimiter: "" precision: 1 storage_units: format: "%n %u" units: byte: one: "Byte" other: "Bytes" kb: "KB" mb: "MB" gb: "GB" tb: "TB" # Used in array.to_sentence. support: array: sentence_connector: "and" skip_last_comma: false activerecord: errors: messages: inclusion: "is not included in the list" exclusion: "is reserved" invalid: "is invalid" confirmation: "doesn't match confirmation" accepted: "must be accepted" empty: "can't be empty" blank: "can't be blank" too_long: "is too long (maximum is #{{count}} characters)" too_short: "is too short (minimum is #{{count}} characters)" wrong_length: "is the wrong length (should be #{{count}} characters)" taken: "has already been taken" not_a_number: "is not a number" not_a_date: "is not a valid date" greater_than: "must be greater than #{{count}}" greater_than_or_equal_to: "must be greater than or equal to #{{count}}" equal_to: "must be equal to #{{count}}" less_than: "must be less than #{{count}}" less_than_or_equal_to: "must be less than or equal to #{{count}}" odd: "must be odd" even: "must be even" greater_than_start_date: "must be greater than start date" not_same_project: "doesn't belong to the same workstream" circular_dependency: "This relation would create a circular dependency" actionview_instancetag_blank_option: Please select general_text_No: 'No' general_text_Yes: 'Yes' general_text_no: 'no' general_text_yes: 'yes' general_lang_name: 'English' general_csv_separator: ',' general_csv_decimal_separator: '.' general_csv_encoding: ISO-8859-1 general_pdf_encoding: ISO-8859-1 general_first_day_of_week: '7' notice_account_reactivated: Your account has been reactivated.
    Welcome back!

    Home notice_account_canceled: Your account is now canceled. Thanks for trying us out.

    We would love your feedback on how we can do better.

    feedback@bettermeans.com notice_account_inactive_user: Account has not yet been activated. Please check your email for activation link notice_account_updated: Account was successfully updated. notice_account_invalid_creditentials: Invalid user or password notice_account_password_updated: Password was successfully updated. notice_account_wrong_password: Wrong password notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. notice_account_unknown_email: Unknown user. notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. notice_account_activated: Your account has been activated. You can now log in. notice_successful_create: Successful creation. notice_successful_update: Successful update. notice_successful_delete: Successful deletion. notice_successful_connection: Successful connection. notice_file_not_found: The page you were trying to access doesn't exist or has been removed. notice_locking_conflict: Data has been updated by another user. notice_not_authorized: You are not authorized to access this page. notice_email_sent: "An email was sent to #{{value}}" notice_email_error: "An error occurred while sending mail (#{{value}})" notice_feeds_access_key_reseted: Your RSS access key was reset. notice_failed_to_save_issues: "Failed to save #{{count}} item(s) on #{{total}} selected: #{{ids}}." notice_no_issue_selected: "No item is selected! Please, check the items you want to edit." notice_account_pending: "Your account was created and is now pending administrator approval." notice_default_data_loaded: Default configuration successfully loaded. notice_unable_delete_version: Unable to delete deliverable. notice_issue_done_ratios_updated: Issue done ratios updated. notice_this_is_your_profie: "This is your public profile. To manage your account click here" error_old_invite : "Old or bad invitation" error_bad_email_update : "Poorly formed email update" error_can_t_load_default_data: "Default configuration could not be loaded: #{{value}}" error_issue_not_found_in_project: 'The item was not found or does not belong to this workstream' error_no_tracker_in_project: 'No issue type is associated to this workstream. Please check the Workstream settings.' error_no_default_issue_status: 'No default item status is defined. Please check your configuration (Go to "Administration -> Item statuses").' error_can_not_reopen_issue_on_closed_version: 'An item assigned to a closed deliverable can not be reopened' error_can_not_archive_project: This project can not be archived error_issue_done_ratios_not_updated: "Issue done ratios not updated." error_workflow_copy_source: 'Please select a source tracker or role' error_workflow_copy_target: 'Please select target tracker(s) and role(s)' error_general: 'Something went wrong.' warning_attachments_not_saved: "#{{count}} file(s) could not be saved." mail_subject_lost_password: "Your #{{value}} password" mail_body_lost_password: 'To change your password, click on the following link:' mail_subject_register: "Your #{{value}} account activation" mail_body_register: 'To activate your account, click on the following link:' mail_body_account_information_external: "You can use your #{{value}} account to log in." mail_body_account_information: Your account information mail_subject_account_activation_request: "#{{value}} account activation request" mail_subject_email_update_activation: "#{{value}} new email activation" mail_body_account_activation_request: "A new user (#{{value}}) has registered. The account is pending your approval:" mail_subject_reminder: "#{{count}} item(s) due in the next days" mail_body_reminder: "#{{count}} item(s) that are owned by you are due in the next #{{days}} days:" #updated mail_subject_wiki_content_added: "'#{{page}}' wiki page has been added" mail_body_wiki_content_added: "The '#{{page}}' wiki page has been added by #{{author}}." mail_subject_wiki_content_updated: "'#{{page}}' wiki page has been updated" mail_body_wiki_content_updated: "The '#{{page}}' wiki page has been updated by #{{author}}." gui_validation_error: 1 error gui_validation_error_plural: "#{{count}} errors" field_tags : "Tags" field_points : "Avg estimated credits" field_logo : "Logo" field_image_file: "Upload local file" field_image_file_url: "or image URL" field_retro: Retrospective field_volunteer: Volunteer field_name: Name field_description: Description field_summary: Summary field_is_required: Required field_firstname: Firstname field_lastname: Lastname field_mail: Email field_filename: File field_filesize: Size field_downloads: Downloads field_author: Author field_created_at: Created field_updated_at: Updated field_field_format: Format field_is_for_all: For all workstreams field_possible_values: Possible values field_regexp: Regular expression field_min_length: Minimum length field_max_length: Maximum length field_value: Value field_category: Category field_title: Title field_project: Workstream field_item: Item field_status: Status field_notes: Notes field_is_closed: Item closed field_is_default: Default value field_tracker: Type field_subject: Subject field_due_date: Due by field_expected_date: Expected by field_assigned_to: Owner #Changed field_priority: Priority field_fixed_version: Deliverable field_user: User field_role: Role field_homepage: Homepage field_is_public: Public field_parent: Sub workstream of field_is_in_roadmap: Items displayed in roadmap field_login: Username field_mail_notification: Email Notifications field_admin: Administrator field_last_login_on: Last connection field_language: Language field_effective_date: Date field_password: Password field_new_password: New password field_password_confirmation: Retype Password field_version: Deliverable field_type: Type field_host: Host field_port: Port field_account: Account field_base_dn: Base DN field_attr_login: Login attribute field_attr_firstname: Firstname attribute field_attr_lastname: Lastname attribute field_attr_mail: Email attribute field_onthefly: On-the-fly user creation field_start_date: Start Date field_done_ratio: "% Done" field_auth_source: Authentication mode field_hide_mail: Hide my email address field_active_only_jumps: "Show only workstreams I'm active in for top jump menu" field_comments: Comment field_wiki_comments: Revision notes field_url: URL field_start_page: Home page field_subproject: Sub workstream field_hours: Hours field_activity: Activity field_spent_on: Date field_identifier: Identifier field_is_filter: Used as a filter field_issue_to: Related item field_issue: Related Item field_delay: Delay field_assignable: Items can be owned by this role field_redirect_existing_links: Redirect existing links field_estimated_hours: Estimated time field_column_names: Columns field_time_zone: Time zone field_searchable: Searchable field_default_value: Default value field_comments_sorting: Display comments field_parent_title: Parent page field_parent_project: Parent workstream field_editable: Editable field_watcher: Watcher field_identity_url: OpenID URL field_content: Content field_group_by: Group results by field_sharing: Sharing field_dpp: Estimation Scale field_hourly_rate_per_person: Hourly rate per person field_hourly_cap: Hourly cap field_pri: Pri setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text setting_default_language: Default language setting_login_required: Authentication required setting_self_registration: Self-registration setting_attachment_max_size: Attachment max. size setting_issues_export_limit: Items export limit setting_mail_from: Emission email address setting_bcc_recipients: Blind carbon copy recipients (bcc) setting_plain_text_mail: Plain text mail (no HTML) setting_host_name: Host name and path setting_text_formatting: Text formatting setting_wiki_compression: Wiki history compression setting_feeds_limit: Feed content limit setting_default_projects_public: New workstreams are public by default setting_autofetch_changesets: Autofetch commits setting_commit_ref_keywords: Referencing keywords setting_commit_fix_keywords: Fixing keywords setting_autologin: Autologin setting_date_format: Date format setting_time_format: Time format setting_cross_project_issue_relations: Allow cross-workstream item relations setting_issue_list_default_columns: Default columns displayed on the item list setting_commit_logs_encoding: Commit messages encoding setting_emails_footer: Emails footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format setting_activity_days_default: Days displayed on workstream activity setting_display_subprojects_issues: Display sub workstreams items on main workstreams by default setting_enabled_scm: Enabled SCM setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" setting_mail_handler_api_enabled: Enable WS for incoming emails setting_mail_handler_api_key: API key setting_sequential_project_identifiers: Generate sequential workstream identifiers setting_gravatar_enabled: Use Gravatar user icons setting_gravatar_default: Default Gravatar image setting_diff_max_lines_displayed: Max number of diff lines displayed setting_file_max_size_displayed: Max size of text files displayed inline setting_openid: Allow OpenID login and registration setting_password_min_length: Minimum password length setting_new_project_user_role_id: Role given to a non-admin user who creates a workstream setting_default_projects_modules: Default enabled modules for new workstreams setting_issue_done_ratio: Calculate the item done ratio with setting_issue_done_ratio_issue_field: Use the item field setting_issue_done_ratio_issue_status: Use the item status setting_start_of_week: Start calendars on setting_rest_api_enabled: Enable REST web service permission_view_project : View workstream permission_add_project: Create workstream permission_add_subprojects: Create sub workstream permission_edit_project: Edit workstream permission_move_project: Move workstream permission_select_project_modules: Select workstream modules permission_manage_members: Manage members permission_manage_versions: Manage deliverables permission_manage_categories: Manage item categories permission_add_issues: Add items permission_edit_issues: Edit items permission_manage_issue_relations: Manage item relations permission_add_issue_notes: Add notes permission_edit_issue_notes: Edit notes permission_edit_own_issue_notes: Edit own notes permission_move_issues: Move items permission_delete_issues: Delete items permission_manage_public_queries: Manage public queries permission_save_queries: Save queries permission_view_gantt: View gantt chart permission_view_calendar: View calendar permission_view_issue_watchers: View watchers list permission_add_issue_watchers: Add watchers permission_delete_issue_watchers: Delete watchers permission_log_time: Log spent time permission_view_time_entries: View spent time permission_edit_time_entries: Edit time logs permission_edit_own_time_entries: Edit own time logs permission_manage_news: Manage news permission_comment_news: Comment news permission_manage_documents: Manage documents permission_view_documents: View documents permission_manage_files: Manage files permission_view_files: View files permission_manage_wiki: Manage wiki permission_rename_wiki_pages: Rename wiki pages permission_delete_wiki_pages: Delete wiki pages permission_view_wiki_pages: View wiki permission_view_wiki_edits: View wiki history permission_edit_wiki_pages: Edit wiki pages permission_delete_wiki_pages_attachments: Delete attachments permission_protect_wiki_pages: Protect wiki pages permission_view_changesets: View changesets permission_commit_access: Commit access permission_manage_boards: Manage discussions permission_view_messages: View messages permission_add_messages: Post messages permission_edit_messages: Edit messages permission_edit_own_messages: Edit own messages permission_delete_messages: Delete messages permission_delete_own_messages: Delete own messages project_module_issue_tracking: Item tracking project_module_time_tracking: Time tracking project_module_news: News project_module_documents: Documents project_module_files: Files project_module_wiki: Wiki project_module_boards: Discussions title_member_nomination : "Motion to elect #{{user}} as a member" title_core_member_nomination : "Motion to elect #{{user}} to the Core Team" title_core_member_removal : "Motion to remove #{{user}} from the Core Team" title_member_removal : "Motion to remove #{{user}} as a member" label_usage_over : "Your usage is over your plan's limits" label_trial_expired : "Your trial period has expired" label_email_update: Your new email address label_new_page: New page label_my_issues: My items label_joined_issues: Joined label_joined: Joined label_projects_recent: Recent workstreams label_projects_active_in: Workstreams I'm active in label_projects_all : All my workstreams label_view_all : View all label_track_invites: Track sent invites label_unarchive_project: Un-archive workstream label_unarchive_project_brackets: (unarchive) label_archive_project: Archive workstream label_delete_project: Delete workstream label_move_project: Move workstream label_note_to_recipient: Note for recipient (optional) label_amount_to_transfer: Amount to Transfer label_select_recipient: Select Recipient label_credit_transfers: Credit Transfers label_credit_transfer: "Transfer credits" label_new_credit_transfer: New Transfer label_select_project: Select Project Workstream label_previous_credit_transfers: Your History label_data_dump: Data Dump text_data_dump: Download a local copy of all issues that belong to workstreams that you own label_issues: datadump.csv label_type_of_workstream: Workstream Options label_your_gravatar: Profile Picture label_team_with_access: Members with clearance (to this workstream) label_select_plan: Select New Plan label_current_plan: Current Plan label_user: User label_user_plural: Users label_user_new: New user label_user_anonymous: Anonymous label_project: Workstream label_project_new: New workstream label_project_plural: Workstreams label_x_projects: zero: no workstreams one: 1 workstream other: "#{{count}} workstreams" label_project_all: All Workstreams label_project_latest: Latest Workstreams label_enterprise_latest: Latest Public Workstreams label_enterprise_active: Most Active Public Workstreams label_enterprise_plural: Open enterprises label_issue: Item label_issue_new: New item label_issue_plural: Items label_issue_view_all: View all items label_issues_by: "Items by #{{value}}" label_issue_added: Item added label_issue_updated: Item updated label_document: Document label_document_new: New document label_document_plural: Docs label_document_added: Document added label_quick_links: Quick links label_role: Role label_role_plural: Roles label_role_new: New role label_role_and_permissions: Roles and permissions label_member: Member label_member_new: New member label_member_plural: Members label_team : Team label_team_plural : Teams label_active_team : Active people label_dashboard : Dashboard label_tracker: Type label_tracker_plural: Types label_tracker_new: New type label_workflow: Workflow label_issue_status: Item status label_issue_status_plural: Item statuses label_issue_status_new: New status label_enumerations: Enumerations label_enumeration_new: New value label_information: Information label_information_plural: Information label_personal_information: Personal Info label_billing_information: Billing Info label_please_login: Please log in label_register: Sign up label_login_with_open_id_option: or login with OpenID label_password_lost: Lost password label_home: Home label_my_home: My Home label_my_page: Summary label_new_project: New Workstream label_my_account: Account label_my_projects: My workstreams label_projects_i_belong_to: Workstreams I belong to label_projects_i_started: Workstreams I started label_activities_i_belong_to: Recent activity label_administration: Administration label_login: Sign in label_logout: Sign out label_help: Help label_reported_issues: Reported items label_assigned_to_me_issues: Committed label_added_issues: Created label_recent_issues: Recent label_assigned: Own label_last_login: Last connection label_registered_on: Signed up on label_activity: Activity label_overall_activity: Overall activity label_user_activity: "#{{value}}'s activity" label_new: New label_logged_as: Logged in as label_environment: Environment label_authentication: Authentication label_auth_source: Authentication mode label_auth_source_new: New authentication mode label_auth_source_plural: Authentication modes label_subproject : Sub workstream label_subproject_plural: Sub workstreams label_subproject_new: New sub workstream label_new_item_in: New item in label_and_its_subprojects: "#{{value}} and its sub workstreams" label_waiting_for_you: "#{{notifications}} waiting for you" label_min_max_length: Min - Max length label_list: List label_date: Date label_integer: Integer label_float: Float label_boolean: Boolean label_string: Text label_text: Long text label_attribute: Attribute label_attribute_plural: Attributes label_download: "#{{count}} Download" label_download_plural: "#{{count}} Downloads" label_no_data: Nothing here yet label_no_credits: You have no credits to transfer label_change_status: Change status label_history: History label_attachment: File label_attachment_new: New file label_attachment_delete: Delete file label_attachment_plural: Files label_file_added: File added label_report: Report label_report_plural: Reports label_news: News label_news_new: Add news label_news_plural: News label_news_latest: Latest news label_news_view_all: View all news label_activity_latest: Latest activity label_activity_view_all: View all activity label_news_added: News added label_settings: Settings label_overview: Overview label_version: Deliverable label_version_new: New deliverable label_version_plural: Deliverables label_confirmation: Confirmation label_export_to: 'Also available in:' label_read: Read... label_public_projects: Public workstreams label_open_issues: open label_open_issues_plural: open label_closed_issues: closed label_closed_issues_plural: closed label_x_open_issues_abbr_on_total: zero: 0 open / #{{total}} one: 1 open / #{{total}} other: "#{{count}} open / #{{total}}" label_x_open_issues_abbr: zero: 0 open one: 1 open other: "#{{count}} open" label_x_closed_issues_abbr: zero: 0 closed one: 1 closed other: "#{{count}} closed" label_total: Total label_permissions: Permissions label_current_status: Current status label_new_statuses_allowed: New statuses allowed label_all: all label_none: none label_nobody: nobody label_next: Next label_previous: Previous label_used_by: Used by label_details: Details label_add_note: Add a note label_per_page: Per page label_calendar: Calendar label_months_from: months from label_gantt: Gantt label_internal: Internal label_last_changes: "last #{{count}} changes" label_change_view_all: View all changes label_personalize_page: Personalize this page label_comment: Comment label_comment_plural: Comments label_x_comments: zero: no comments one: 1 comment other: "#{{count}} comments" label_comment_add: Add a comment label_comment_added: Comment added label_comment_delete: Delete comments label_query: Custom query label_query_plural: Custom queries label_query_new: New query label_filter_add: Add filter label_filter_plural: Filters label_equals: is label_not_equals: is not label_in_less_than: in less than label_in_more_than: in more than label_greater_or_equal: '>=' label_less_or_equal: '<=' label_in: in label_today: today label_Today: Today label_same_day: Same day label_all_time: all time label_yesterday: yesterday label_this_week: this week label_last_week: last week label_last_n_days: "last #{{count}} days" label_this_month: this month label_last_month: last month label_this_year: this year label_date_range: Date range label_less_than_ago: less than days ago label_more_than_ago: more than days ago label_ago: days ago label_ago_simple: ago label_contains: contains label_not_contains: doesn't contain label_day: day label_day_plural: days label_browse: Browse label_browse_workstreams: Browse bettermeans label_browse_activity: Browse activity label_modification: "#{{count}} change" label_modification_plural: "#{{count}} changes" label_branch: Branch label_tag: Tag label_revision: Revision label_revision_plural: Revisions label_revision_id: "Revision #{{value}}" label_associated_revisions: Associated revisions label_added: added label_modified: modified label_copied: copied label_renamed: renamed label_deleted: deleted label_moved: moved label_archived: archived label_unarchived: un-archived label_project_description: Description label_message: Discussion label_latest_revision: Latest revision label_latest_revision_plural: Latest revisions label_view_revisions: View revisions label_view_all_revisions: View all revisions label_max_size: Maximum size label_sort_highest: Move to top label_sort_higher: Move up label_sort_lower: Move down label_sort_lowest: Move to bottom label_roadmap: Roadmap label_roadmap_due_in: "Due in #{{value}}" label_roadmap_overdue: "#{{value}} late" label_roadmap_no_issues: No items for this deliverable label_search: Search label_result_plural: Results label_all_words: All words label_wiki: Wiki label_wiki_edit: Wiki edit label_wiki_edit_plural: Wiki edits label_wikipage: Wiki page label_wiki_page: Wiki page label_wiki_page_plural: Wiki pages label_index_by_title: Index by title label_index_by_date: Index by date label_current_version: Current deliverable label_preview: Preview label_feed_plural: Feeds label_changes_details: Details of all changes label_issue_tracking: Work Breakdown label_boards: Discussion label_spent_time: Spent time label_f_hour: "#{{value}} hour" label_f_hour_plural: "#{{value}} hours" label_time_tracking: Time tracking label_change_plural: Changes label_change : Change label_statistics: Statistics label_commits_per_month: Commits per month label_commits_per_author: Commits per author label_view_diff: View differences label_diff_inline: inline label_diff_side_by_side: side by side label_options: Options label_copy_workflow_from: Copy workflow from label_permissions_report: Permissions report label_watched_issues: Watched label_watched: Watched label_related_issues: Related items label_applied_status: Applied status label_loading: Working... label_relation_new: New relation label_relation_delete: Delete relation label_relates_to: related to label_duplicates: duplicates label_duplicated_by: duplicated by label_blocks: blocks label_blocked_by: blocked by label_precedes: precedes label_follows: follows label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start label_start_to_end: start to end label_stay_logged_in: Stay logged in label_disabled: disabled label_show_completed_versions: Show completed deliverables label_me: me label_board: Discussion label_board_new: New discussion label_board_plural: Discussions label_topic_plural: Topics label_message_plural: Messages label_message_last: Last message label_message_new: New message label_topic_new: New topic label_message_posted: Message added label_reply_plural: Replies label_comments_plural: Comments label_send_information: Send account information to the user label_year: Year label_month: Month label_week: Week label_date_from: From label_date_to: To label_language_based: Based on user's language label_sort_by: "Sort by #{{value}}" label_send_test_email: Send a test email label_feeds_access_key: RSS access key label_missing_feeds_access_key: Missing a RSS access key label_feeds_access_key_created_at: "RSS access key created #{{value}} ago" label_module_plural: Modules label_added_time_by: "Added by #{{author}} #{{age}} ago" label_updated_time_by: "Updated by #{{author}} #{{age}} ago" label_created_time_by: "Created #{{age}} ago by" label_updated_time: "Updated #{{value}} ago" label_jump_to_a_project: MY WORKSTREAMS label_file_plural: Files label_changeset_plural: Changesets label_default_columns: Default columns label_no_change_option: (No change) label_bulk_edit_selected_issues: Bulk edit selected items label_theme: Theme label_default: Default label_search_titles_only: Titles only label_user_mail_option_all: "For any event on all my workstreams" label_user_mail_option_selected: "For any event on the selected workstreams only..." label_user_mail_option_none: "Only for things I watch or I'm involved in" label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" label_user_mail_no_emails: "Disable email notification" label_user_mail_daily_digest: "Send me a daily summary of system updates to issues I'm involved in" label_registration_activation_by_email: account activation by email label_registration_manual_activation: manual account activation label_registration_automatic_activation: automatic account activation label_display_per_page: "Per page: #{{value}}" label_age: Age label_change_properties: Change properties label_general: General label_more: More label_scm: SCM label_plugins: Plugins label_ldap_authentication: LDAP authentication label_downloads_abbr: D/L label_optional_description: Optional description label_add_another_file: Add another file label_preferences: Preferences label_chronological_order: In chronological order label_reverse_chronological_order: In reverse chronological order label_planning: Planning label_incoming_emails: Incoming emails label_generate_key: Generate a key label_issue_watchers: Watchers label_example: Example label_display: Display label_sort: Sort label_ascending: Ascending label_descending: Descending label_date_from_to: From #{{start}} to #{{end}} label_wiki_content_added: Wiki page added label_wiki_content_updated: Wiki page updated label_group: Group label_group_plural: Groups label_group_new: New group label_time_entry_plural: Spent time label_commit_request_history: Ownership History #new label_by: "by #{{author}} #{{age}} ago" #new label_requested_by: "Requested by #{{author}} #{{age}} ago" #new label_offered_by: "Offered by #{{author}} to #{{responder}} #{{age}} ago" #new label_accepted_by: "Accepted by #{{responder}} #{{age}} ago" #new label_declined_by: "Declined by #{{responder}} #{{age}} ago" #new label_from_ago: "from #{{author}} #{{age}} ago" #new label_taken_by: "Taken by #{{responder}} #{{age}} ago" #news label_declined: "Declined #{{age}} ago" #new label_accepted: "Accepted #{{age}} ago" #new label_recinded: "Withdrawn #{{age}} ago" #new label_released: "Released #{{age}} ago" #new label_not_sure: "Not sure" label_commitment: "Commitment" label_commitment_offer: "Ownership Offer" label_commitment_request: "Ownership Request" label_notification : "Notification" label_notifications : "Notifications" label_new_notification : "notification" label_new_notifications : "notifications" label_response_sent : "Response sent" label_response_ignored : "Request ignored" label_you : "you" label_for : "for" label_from : "from" label_ignored : "Ignored" label_marked_as_read : "Marked as read" label_ownership_offered : "Ownership offered" label_ownership_offered_to : "Ownership offered to #{{user}}" label_ownership_offer_declined : "Ownership offer declined" label_ownership_offer_accepted : "Ownership offer accepted" label_ownership_offer_declined_from : "#{{user}}'s ownership offer was declined" label_ownership_offer_accepted_from : "#{{user}}'s ownership offer was accepted" label_ownership_offer_withdrawn : "Ownership offer withdrawn" label_ownership_offer_withdrawn_to : "Ownership offer to #{{user}} was withdrawn" label_ownership_requested : "Ownership requested" label_ownership_request_accepted : "Ownership request accepted" label_ownership_request_auto_accepted : "Ownership request auto accepted" label_ownership_request_declined : "Ownership request declined" label_ownership_request_accepted_from : "#{{user}}'s ownership request accepted" label_ownership_request_declined_from : "#{{user}}'s ownership request declined" label_ownership_request_recinded : "Ownership request recinded" label_ownership_released : "Ownership released" label_ownership_taken : "Ownership taken" label_commit_request_plural : "Task ownership" label_team_offer : "Core Team Invitation" label_team_offer_plural : "Team members" label_team_plural : "Team" label_team_invitation : "Core Team Invitation" label_team_request : "Core Team Request" label_team_request_plural : "Core Team requests" label_accepted : "accepted" label_sent : "sent" label_withdrawn : "withdrawn" label_declined : "declined" label_to_join_core_team_of : "to join the #{{project}} core team" label_request : "Request" label_invitation : "Invitation" label_was : "was" label_your : 'Your' label_to : "to" label_note_from : "Note from #{{author}}" label_sending_team_invitation : "#{{author}} wants you to join the Core Team for the #{{project}} workstream" label_sending_team_request : "#{{author}} wants to join the Core Team for the #{{project}} workstream" label_team_view_all : "View team details" label_team_view_all_enterprise : "View entire team" label_member_role_plural : "Team Changes" label_join_core_team : "Join Core Team" label_leave_core_team : "Leave Core Team" label_request_to_join_core_team : "Request to join Core Team" label_invitation_to_join_core_team : "Invite to Core Team" label_team_points : "Core Points" label_share : "Share" label_share_plural : "Shares" label_credit_plural : "Credits" label_credit : "Credit" label_retro_started : "Retrospective Started" label_retro_ended : "Retrospective Ended" label_motion_started : "New motion: #{{title}}" label_credits : Credits label_shares : Shares label_credit_queue : Credit Queue (Active Credits) label_credit_history : Credit History (All Credits) label_my_credits : My Credits label_credits_breakdown : Credit Breakdown label_active_credits : Active Credits label_outstanding_credits : Oustanding Credits label_total_credits_issued : Total Issued label_youve_got_credits : "You've got credits!" label_someone_joined_your_issue : "#{{joiner}} joined an issue assigned to you" label_someone_added_you_to_their_issue : "#{{sender}} added you to an issue they own" label_someone_removed_you_from_their_issue : "#{{sender}} removed you from an issue they own" label_someone_left_your_issue : "#{{joiner}} left an issue assigned to you" label_someone_mentioned_you : "#{{mentioner}} mentioned you" label_agree : "Agree" label_disagree : "Disagree" label_block : "Block" label_neutral : "Neutral" label_history : "History" label_select : "Select" label_no_mention : "No unread mentions" label_mentions : "Mentions" label_version_sharing_none: Not shared label_version_sharing_descendants: With workstreams label_version_sharing_hierarchy: With workstream hierarchy label_version_sharing_tree: With workstream tree label_version_sharing_system: With all workstreams label_update_issue_done_ratios: Update item done ratios label_copy_source: Source label_copy_target: Target label_copy_same_as_target: Same as target label_display_used_statuses_only: Only display statuses that are used by this tracker label_api_access_key: API access key label_missing_api_access_key: Missing an API access key label_api_access_key_created_at: "API access key created #{{value}} ago" label_dpp_scale: "Estimation Scale" label_motion: "Motion" label_motions_new: New motion label_motion_state0: "In Progress" label_motion_state1: "Passed" label_motion_state2: "Defeated" label_motion_state3: "Cancelled" label_motion_vote_type1 : "Unanimous Consensus" label_motion_vote_type2 : "Lazy Majority" label_motion_vote_type3 : "Share Majority" label_motion_vote_type1_desc : "no blocks or disagrees" label_motion_vote_type2_desc : "more agrees than disagree, no blocks" label_motion_vote_type3_desc : "1 share = 1 vote. minimum 2/3 agree" label_motion_vote_action1 : "Block" label_motion_vote_action9999 : "Disagree" label_motion_vote_action10000 : "Neutral" label_motion_vote_action10001 : "Agree" label_motion_vote_action_share_agree : "Agree with #{{points}} shares" label_motion_vote_action_share_disagree : "Disagree with #{{points}} shares" label_motion_vote_action_share_neutral : "Neutral with #{{points}} shares" label_you_voted : "You voted: #{{action}}" label_voted : "voted" label_you_havent_voted : "You haven't cast your vote yet" label_tally : "Tally" label_motion_variation0 : "General Motion" label_motion_variation1 : "Extraordinary Motion" label_motion_variation2 : "New Member Nomination" label_motion_variation3 : "New Core Team Member Nomination" label_motion_variation4 : "Motion to Remove Member" label_motion_variation5 : "Motion to Remove Core Team Member" label_motion_variation6 : "Board Motion (public)" label_motion_variation7 : "Board Motion (private)" label_motion_variation8 : "Motion to Add Hourly Activity Type" label_binding : "Binding" label_non_binding : "Non binding" label_agree_total : "Agree Total" label_disagree_total : "Disagree Total" label_hidden : "(user hidden)" label_motions_active : "Active Motions" label_motions_view_all : "View all Motions" label_new_role : "New role: #{{role_name}}" 5label_new_invitation : "Invitation to join #{{project_name}} as a #{{role_name}}" label_drop_from_core_team : "Remove from Core" label_nominate_to_core_team : "Nominate to Core" label_drop_from_member : "Remove Membership" label_nominate_to_member : "Nominate to Membership" label_reputation : "Reputation Matrix" label_assessment_accuracy : "Assessment Accuracy" label_hourly_type: "Hourly type" label_hourly_type_plural: "Hourly types" label_hourly_type_new: "New hourly type" label_hourly_rate_per_person: "Hourly rate per person" label_hourly_cap: "Hourly cap" label_invitation_new: "Invite people" label_invitation_others: "Invite others" label_all_activity: "Browse all activity" label_cancel_account: "Cancel my account" field_b_first_name: "First name" field_b_last_name: "Last name" field_b_address1: "Street Address" field_b_address2: "Address (line 2)" field_b_city: "City" field_b_state: "State" field_b_zip: "Zip/Postal Code" field_b_country: "Country" field_b_phone: "Phone" field_b_cc_type: "Type" field_b_cc_month: "Expiration Date" field_b_cc_year: "" field_b_cc_last_four: "Credit Card" label_b_expiration: "Expiration Date" label_b_verification_code: "Verification Code" label_memberrole: "membership" button_upgrade : Upgrade button_update_billing : Update Billing button_accept_invitation : Accept Invitation button_working : working... button_change_plan: Change Plan button_update_billing: Update billing button_login: Login button_submit: Submit button_search: Search button_save: Save button_check_all: Check all button_uncheck_all: Uncheck all button_delete: Delete button_create: Create button_resend: Resend button_create_and_continue: Create and continue button_test: Test button_edit: Edit button_add: Add button_add_team_member: + Add Someone button_change: Change button_apply: Apply button_clear: Clear button_lock: Lock button_unlock: Unlock button_download: Download button_list: List button_view: View button_move: Move button_move_and_follow: Move and follow button_back: Back button_cancel: Cancel button_close: Close button_activate: Activate button_sort: Sort button_log_time: Log time button_rollback: Rollback to this deliverable button_watch: Watch button_unwatch: Unwatch button_reply: Reply button_reply: Comment button_archive: Archive button_unarchive: Unarchive button_reset: Reset button_rename: Rename button_change_password: Change password button_copy: Copy button_copy_and_follow: Copy and follow button_annotate: Annotate button_update: Update button_comment: Comment button_signup: Sign up button_configure: Configure button_quote: Quote button_participate: Participate button_request_commitment_request: Request Ownership #new button_request_commitment_take: Take Ownership #new button_request_commitment_remove: Withdraw Ownership Request #new button_request_commitment_withdraw: Withdraw #new button_request_commitment_remove_offer: Withdraw Ownership Offer #new button_request_commitment_giveup: Release Ownership #new button_request_commitment_transfer: Transfer Ownership #new button_request_commitment_accept: Accept #new button_request_commitment_decline: Decline #new button_request_commitment_offer: Offer Ownership #new button_team_offer_request: Request to join Core Team #new button_team_offer_take: Get on the Core Team #new button_team_offer_withdraw_request: Withdraw Core Team request #new button_team_offer_withdraw_invitation: Withdraw Core Team invitation #new button_team_offer_giveup: Resign from Core Team #new button_team_offer_accept: Accept #new button_team_offer_decline: Decline #new button_team_offer_invite: Send invitation to join Core Team #new button_ignore: Ignore button_mark_as_read: Mark as read button_duplicate: Duplicate button_show: Show button_activate: Activate button_deactivate: Deactivate button_dashboard: Dashboard status_active: active status_registered: registered status_locked: locked version_status_open: open version_status_locked: locked version_status_closed: closed field_active: Active field_body: Body field_joined_by: Joined by field_item_url: Permalink text_invite_role_instruction: Invite people to this role text_resend_invite_message: "Add a personal comment to the invitation reminder" text_reset_link: "Reset invitation code" text_personalize_invitations: "Personalize your invitation" text_email_instruction: "Enter up to 50 emails, one email per line" text_invite_others_to_contribute: "Invite anyone to contribute to" text_invite_others_to_contribute_personal: "OR - Send personalized, individual invitations" text_confirm_delete_project: "Are you sure you want to delete this workstream? This action cannot be undone!" text_confirm_cancel_account: "Are you sure you want to cancel your account? This action cannot be undone!" text_confirm_archive_project: "Are you sure you want to archive this workstream? Nobody will be able to see it anymore. You can unarchive it in the future." text_confirm_unarchive_project: "Are you sure you want to unarchive this workstream?" text_included_note: note text_failed_to_transfer: Failed to transfer credits.
    text_your_gravatar: We use Gravatar to display your profile picture.
    To add/update your picture create an account here using the same email you used to signup for BetterMeans text_select_mail_notifications: Select actions for which email notifications should be sent. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 means no restriction text_project_destroy_confirmation: Are you sure you want to delete this workstream and related data ? text_subprojects_destroy_warning: "Its sub workstream(s): #{{value}} will be also deleted." text_workflow_edit: Select a role and a type to edit the workflow text_are_you_sure: Are you sure ? text_journal_changed: "#{{label}} changed from #{{old}} to #{{new}}" text_journal_set_to: "#{{label}} set to #{{value}}" text_journal_deleted: "#{{label}} deleted (#{{old}})" text_journal_added: "#{{label}} #{{value}} added" text_tip_task_begin_day: task beginning this day text_tip_task_end_day: task ending this day text_tip_task_begin_end_day: task beginning and ending this day text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.
    Once saved, the identifier can not be changed.' text_caracters_maximum: "#{{count}} characters maximum." text_caracters_minimum: "Must be at least #{{count}} characters long." text_length_between: "Length between #{{min}} and #{{max}} characters." text_tracker_no_workflow: No workflow defined for this type text_unallowed_characters: Unallowed characters text_comma_separated: Multiple values allowed (comma separated). text_issues_ref_in_commit_messages: Referencing and fixing items in commit messages text_issue_added: "Item #{{id}} has been reported by #{{author}}." text_issue_updated: "Item #{{id}} has been updated by #{{author}}." text_line_separated: Multiple values allowed (one line for each value). text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ? text_user_mail_option: "For unselected workstreams, you will only receive notifications about things you watch or you're involved in (eg. items you're the author or owner)." #updated text_no_configuration_data: "Roles, types, item statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." text_load_default_configuration: Load the default configuration text_status_changed_by_changeset: "Applied in changeset #{{value}}." text_issues_destroy_confirmation: 'Are you sure you want to delete the selected item(s) ?' text_select_project_modules: 'Select modules to enable for this workstream:' text_default_administrator_account_changed: Default administrator account changed text_file_repository_writable: Attachments directory writable text_plugin_assets_writable: Plugin assets directory writable text_rmagick_available: RMagick available (optional) text_destroy_time_entries_question: "#{{hours}} hours were reported on the items you are about to delete. What do you want to do ?" text_destroy_time_entries: Delete reported hours text_assign_time_entries_to_project: Assign reported hours to the workstream text_reassign_time_entries: 'Reassign reported hours to this item:' text_user_wrote: "#{{value}} wrote:" text_enumeration_destroy_question: "#{{count}} objects are assigned to this value." text_enumeration_category_reassign_to: 'Reassign them to this value:' text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them." text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' text_wiki_page_destroy_question: "This page has #{{descendants}} child page(s) and descendant(s). What do you want to do?" text_wiki_page_nullify_children: "Keep child pages as root pages" text_wiki_page_destroy_children: "Delete child pages and all their descendants" text_wiki_page_reassign_children: "Reassign child pages to this parent page" text_create_dialogue_choose_days: "How much time to you need to get this done
    (from the time you own it)?" text_select_user_dialogue_choose_user: "Choose someone to send this request to" text_you_are_the_new_owner_of : "You are the new proud owner of" text_is_asking_ownership_for : "is asking for ownership of a task you" text_is_offering_ownership_for : "is offering you ownership of a task" text_ownership_request_declined : "Ownership request declined" text_ownership_request_accepted : "Ownership request accepted" text_your_ownsership_request_declined_for : "You request for ownership has been declined for " text_ownership_offer_accepted : "Ownership offer accepted" text_your_offer_was_accepted_for : "Your offer was accepted for " text_ownership_offer_declined : "Ownership offer declined" text_your_offer_was_declined_for : "Your offer was declined for " text_loading_dashboard : "Loading dashboard data..." text_loading_dashboard_error : "Failed to load data... please check your internet connectivity and refresh" text_retro_started_for : "A retrospective for the " text_retro_started : " workstream has started.
    You have 3 days to give your team feedback on their contribution" text_retro_ended_for : "A retrospective for the" text_retro_ended : " workstream has ended.
    Click below to see your team's feedback, and your final contribution" text_credits_transferred: "#{{sender}} just transferred #{{amount}} credits to you in #{{project}}" text_credits_distributed_for : "You earned #{{amount}} credits for your work on #{{workstream}}. Congrats!" text_credits_are_a_gift : "These credits were gifted to you by your peers for your contribution. They're not a part of any retrospective." text_credits_are_an_expense : "These credits are a reimbursment for an expense item. They're not a part of any retrospective." text_joiner_is_now_working_with_you : "#{{joiner}} is now working with you on
    #{{issue}}" text_joiner_is_no_longer_working_with_you : "#{{joiner}} is no longer working with you on
    #{{issue}}" text_you_are_now_working_with_you : "You are now part of the team working on
    #{{issue}}" text_you_are_no_longer_working_on : "You are no longer part of the team working on
    #{{issue}}" text_history_hidden_until_you_vote : "Voting history is hidden since you haven't cast your vote" text_tally_hidden_until_you_vote : "Voting tally is hidden until you cast your vote" text_you_dont_have_shares_to_vote : "You need to have shares to weigh in on this motion." text_you_are_now_a_role : "You are now a #{{role_name}} of" text_role_description_Administrator : "As an Admin, you can bypass governance rules and directly add/remove members and workstreams.
    Use this role sparingly as you are setting up your structures,
    and then remove yourself from the admin role when you're ready to fully adopt the Open Enterprise governance model." text_role_description_Contributor : "As a contributor, you can now earn credits and own a stake in #{{enterprise}}.
    Your votes on all matters will be visible to everyone, and will be non-binding." text_role_description_Member : "As a member, all your votes are now binding.
    In addition you can nominate contributors for membership,
    and vote on Core Team elections." text_role_description_Core Team : "As a member of the Core Team you have binding votes in all matters,
    including the selection of new members.
    Congratulations!" text_role_description_Core_Team : "As a member of the Core Team you have binding votes in all matters,
    including the selection of new members.
    Congratulations!" text_role_description_Board : "As a board member,all your votes are binding." text_member_nomination : "#{{user}} is being nominated to be a member of #{{enterprise}}. As a member, all their votes would be binding. In addition they will be able to nominate contributors for membership, and vote on Core Team elections. The votes on this motion will not be visible to to #{{user}}, but they will be able to see the discussion." text_core_member_nomination : "#{{user}} is being nominated for the Core Team of #{{enterprise}}. As a member of the Core Team they would have binding votes in all matters, including the selection of new members. The votes on this motion will not be visible to to #{{user}}, but they will be able to see the discussion." text_core_member_removal : "This motion is to remove #{{user}} from the Core Team of #{{enterprise}}. If the motion passes #{{user}} would become a Member. As a Member, their votes will remain binding and their shares and credits would be unaffected. The votes on this motion will not be visible to to #{{user}}, but they will be able to see the discussion." text_member_removal : "This motion is to remove #{{user}} as a member of #{{enterprise}}. If the motion passes #{{user}} would become a Contributor. As a Contributor, their votes will be non-binding. Their shares and credits would be unaffected. The votes on this motion will not be visible to to #{{user}}, but they will be able to see the discussion." text_issue_edit_footer : "You can comment on this issue via email, by replying to this email directly." text_message_edit_footer : "You can reply to this message via email, by replying to this email directly." text_invitation_note_default : "Hey,\r\n\r\nWe've been using bettermeans.com to work on #{{project}}.\r\n\r\nBettermeans lets us work together as equals, and is a transparent, democratic way to get things done together. \r\n\r\nPlease accept this invitation to join us.\r\n\r\nWarm regards\r\n#{{user}}" text_you_are_invited : "You are being invited to join" text_generic_invitation : "This link allows anybody to get started as a contributor to this workstream." text_generic_invitation_2 : "You can also use the form below to send personalized invitations for folks to join as other roles" text_generic_invitation_long : "The link below allows anybody to become a contributor to this workstream.

    It can be used an infinite amount of times. And doesn't expire until you change it.
    Send it out via email, or post it somewhere where folks can follow it to join your workstream." text_personal_invitation : "Use the form below to send personalized invitations to potential team members.

    This is the better way to go since people are more likely to respond to an exclusive invite, and don't need to validate their email.

    This is also the more secure option since each invitation can be used only once and expires after a while." text_active_team_explanation : "Active Members (for this workstream)

    Active members are people that have been involved in this specific workstream in the past 2 weeks.

    Contributors, members, and core team roles are defined at the parent(root) workstream and apply to all it's sub workstreams.

    For example: if someone is a core team member of a workstream then they will have the same role for all sub workstreams. However, they might be active in some and not others." text_email_updated : "Email successfully updated" text_usage_over : "You have exceeded your usage for your current plan.
    Please upgrade as soon as possible." text_trial_expired : "Your trial period has expired.
    Please update your billing info so we can start charging you." text_role_invitation_description_Administrator : "Admins have the most power.

    An Admin can bypass governance rules and directly add/remove members and workstreams, and all their votes are binding.

    Use this role sparingly as you are setting up your structures. Then transition to using other roles for day to day collaboration." text_role_invitation_description_Contributor : "This the first level of participation in an open enterprise.

    Contributors' votes on all matters will be visible to everyone, and will be non-binding.

    Meaning a contributor's voice is here, but their voting doesn't directly affect the items they are weighing in on.

    A contributor can earn credits and contribute to a workstream." text_role_invitation_description_Member : "Members are at the heart of an open enterprise.

    All members' votes are binding.

    In addition members can nominate contributors for membership, and vote on Core Team elections.

    Only invite members whose principles and values are aligned with yours." text_role_invitation_description_Core_Team : "A member of the Core Team has binding votes in all matters, and is very similar to a member.

    The main difference is a Core Team member can vote on the election of new members.

    Essentially, they are the custodians of the organization's principles, and hold the bar for membership.

    Core Team Members are accountable to all members, and their role is to represent their voice." text_role_invitation_description_Board : "As a board member,all your votes are binding, and you have equal priveleges to a Core Team Member.

    Being on the board though, indicates a lesser commitment to being involved in day to day work." text_project_locked: "This workstream is temporarily locked due to non payment." default_role_administrator: Administrator default_role_citizen: Core Member default_role_contributor: Contributor default_tracker_task: Task default_tracker_subtask: Subtask default_issue_status_new: New default_issue_status_open: Open default_issue_status_assigned: Committed default_issue_status_done: Done default_issue_status_closed: Closed default_issue_status_blocked: Blocked default_issue_status_inprogress: Committed default_issue_status_canceled: Canceled default_issue_status_estimate: Estimate default_issue_status_archived: Archived default_issue_status_accepted: Accepted default_issue_status_rejected: Rejected default_doc_category_public: Public documentation default_doc_category_private: Private documentation default_priority_low: Low default_priority_normal: Normal default_priority_high: High default_priority_urgent: Urgent default_activity_default: Misc default_activity_planning: Planning default_activity_execution: Execution default_issue_tracker_gift: Gift default_issue_tracker_expense: Expense default_issue_tracker_recurring: Recurring default_issue_tracker_hourly: Hourly default_issue_tracker_feature: Feature default_issue_tracker_bug: Bug default_issue_tracker_chore: Chore enumeration_issue_priorities: Item priorities enumeration_doc_categories: Document categories enumeration_activities: Activities (time tracking) enumeration_system_activity: System Activity help_reset_link : Resetting this link changes the invitation code. This deactivates the previous link(s) and prevents anybody from signing in using it.

    This action does not affect the personalized invitations you sent out. help_how_do_i_join_a_team : How do I join this team? help_how_do_i_join_a_team_title : Joining a Team help_how_do_i_join_a_team_description : "There are two levels of team membership: Contributor, and Core Team.

    Becoming a contributor
    Before getting on a core team you must first contribute to the team.
    You become a contributor for a workstream by requesting and getting ownership of an existing tasks.
    Another way to become a contributor, is to be a core team member of any sub workstream.

    Become a core team member
    Once you are a contributor, you may request (or be invited) to join the Core Team by an existing Core Team member.

    You can also join a workstream's core team directly if your a core team member of its parent workstream." help_core_points_title : Core Points help_core_points_description : "Core points are a way of team members endorsing each other, and signaling confidence in each other's abilities and core values.

    When someone on a core team feels that the team is better off with someone on it they can give that person a core point. It also works the other way: If someone feels that someone is hurting the team, they can give them a negative-core point.

    When a member's total core points exceeds zero, they're invited to be on the core team. If their total falls below zero, they revert back to contributor status.

    All voting is confidential to minimize internal politics as much as possible.
    Total core points is calculated by adding up the votes of existing core team members of a workstream and its parent." help_dpp: Items in a workstream are estimated in credits.

    Each workstream has a different scale for possible estimations. The scale varies based on the complexity/granulatiry of the type of work for that workstream. help_select_scale: Items in a workstream are estimated in points.

    Each workstream can have a different payment scale.

    Select a scale that best covers the range of work item sizes. This changes based on the nature of work for a particular workstream. help_assessment_accuracy: "Shows the accuracy with which this user was able to assess their contribution, and their team's contribution in retrospectives.

    Assessment accuracy is averaged over time. More weight is given to the more recent retrospectives, and more weight is given to retrospectives with more credits.

    For example: A postive self assessment accuracy score of +7 means the user tends to overrate themselves by 7 percentage points on average.

    An 'other' assessment score of ± 16 means the user tends have a total bias of plus or minus 16 percentage points when rating their peers.

    The closer both these numbers are to zero, the more accurate the user is in their assessment of self and other. " help_your_assessment: "Your assessment
    Your view of how much you and your team mates contributed to the execution of the items completed in this retrospective overall. What percentage of the work did you accomplish, compared to your team mates.During the retrospective, every team member is asked to assess everyone's contribution. Once the retrospective is complete, the assessments will be averaged out and displayed here." help_team_assessment: "Team assessment
    This is the average percentage contribution the work item as assessed by your peers. Each person's self-assessment is taken out of the average and only their team mates' assessment counts towards this number." help_final_assessment: "All the Team averages are used to be proportionally distributed over 100%" help_confidence: "Assessment confidence
    Adjust this slider depending on how confident you are of your assessment of yourself and your peer.
    Lower confidence, means your ratings will be given less weight, and your self assessment index will be less impact by team-self discrepancies." help_accuracy: "Accuracy and Bias
    This column shows the accuracy with which users who participated in this retrospective where able to assess themselves and their peers." help_item_url: "Item Permalink
    You can copy this link and use it to directly link to this item" help_volunteer: "All work done on this workstream is on a volunteer basis. It will not be compensated for in money, barter, or any other monetary means.

    That's not to say good karma won't come your way!

    This is our small contribution to the volunteer world. Please report any abuse to abuse@bettermeans.com" help_volunteer_credits: "All work done on this workstream is on a volunteer basis. It will not be compensated for in money, barter, or any other monetary means.

    Credits are being used here to track user contribution, and show who did what for this workstream over time. These credits are not a promise for any rewards, or payment" help_no_my_workstreams: "You haven't created any workstreams yet." help_no_workstreams_i_belong_to: "You are not yet a member of any workstreams that you didn't start." help_no_workstreams_active: "You have not been recently active in any workstreams." help_no_workstreams: "You haven't yet gotten involved in any workstreams.
    Create or join something to get started..." help_no_workstreams_archived: "You have no archived workstreams." help_activities_i_belong_to: "This activity stream shows all activity in workstreams that you own and/or belong to" help_this_workstream_is_private: "This is private workstream. When a workstream is made private, all binding members (i.e. members, core team, board, and admins) are given permanent clearance to it. Also, all active contributors to the workstream are given clearance.

    If you want to manage users' clearance to a private workstream, use the members tab under settings." help_issue_tracking: "This is the essential element of the platform in that it allows you to track all work items using the dashboard and item views. However one situation in which you might turn this off is if you are creating a root workstream for your organization and you are planning on creating lots of subworkstreams where all the work and item tracking would take place. This would leave the other elements such as discussion and wiki but without the dashboard or item tracking." help_news: "This module allows you to check out or report on the latest news. News is used to broadcast events to your team. For example winning a bid, or a new product release." help_documents: "This module allows you to upload document to share with your team." help_files: "This module allows you to upload files to share with your team." help_wiki: "Wikis are great tools for collaboration and therefore we provide the option for each workstream to have a wiki. Wikis are essentially online documents and everyone that has access can not only read but also edit and improve online." help_boards: "This module creates a discussion board for your workstream and like so many discussion boards before it, you can talk about stuff here. If someone has an idea that they are not yet ready to propose as a motion or work item and want to talk to folks about it, they can start a new topic. You might use the discussion board to discuss general strategy, new possible workstreams, or anything that comes to mind." help_motions: "Motions are used to make large important decisions that are project-wide. This is a democratic mechanism to make decisions together. There are different types of motions, to learn more about motions click here." help_credits: "Credits are a more open and democratic way of valuing work and awarding compensation. We are really excited about offering this as an option and we strongly encourage you to learn more about credits." help_revision_notes: "Optional: Add notes to this revision of the wiki. These notes will appear in the version history of the wiki" help_mentions: "Mentions happen when someone mentions you by using the '@' symbol before your username in any comments or descriptions.

    For example if somebody adds this comment to an issue:
    'I think @#{{login}} should take a look at this'
    it would appear in your mentions" help_section_project_show: "Welcome to the Overview Page. From here you can get working and invite others to join in the fun. Take a look around
    If you want to start a new workstream and are not sure what to do next or want tips for getting started click here.
    If you are joining an existing workstream and you are unsure what you are looking at, we are here to help, click here.
    We would recommend following the links above and/or taking the tour before you begin." help_section_project_dashboard: "Welcome to your dashboard!|This is where the magic happens. Here you get a full view of all the work within a workstream. Each workstream and subworkstream has its own dashboard.

    Get started by adding a new item (click on the New Item Link at the top of the New column).

    You can also offer new ideas/requests, comment, agree and disagree with ideas, prioritize, and estimate.

    Click on the little key next to the New Item button for a little cheat sheet that shows the meaning of all the icons you see in the Dashboard. As you get rolling with your work, explore the filters and the search functions on the upper right corner.

    To learn more about voting and how it works in the dashboard checkout our help section, or this 9 minute tour of the site.




    " help_section_project_wiki: "Wikis are great tools for collaboration and therefore each workstream has a wiki. You can start building your wiki here and you can create multiple pages by clicking Start Page (in blue to the right). Wikis are essentially online documents and everyone that has access can not only read but also edit and contribute to online.
    Note the different action buttons (on the right in grey), these will be helpful when working on the Wiki." help_section_project_news: "Check out or report on the latest news here. News is used to broadcast events to your team. For example winning a bid, or a new product release." help_section_project_discussion: "Like so many discussion boards before it, you can talk about stuff here. If you have an idea that you are not yet ready to propose as a motion or work item and want to talk to folks about it, start a new topic. You might use the discussion board to discuss general strategy, new possible workstream, new target audience, or anything that comes to mind." help_section_project_activity: "Stay current on what's hot in your work using the activity stream. The activity stream is a very useful way of seeing what is the latest activity in your workstreams and subworkstreams. Any updates or comments or new items or status changes to items basically when anything changes in your workstream, it will appear here. For more information, click here. " help_section_project_team: "How great is your team! Use this page to track the roles people are playing for this workstream. Remember, these roles apply to all sub workstreams as well.

    You can also use the links on the right to invite more team members. Learn more about invitations here.

    There are no job titles in an Open Enterprise, but there are levels of engagement that each come with different voting privileges, learn more about that here.

    " help_section_project_credits: "This page tracks all the credits for your project. Credits are a way of tracking relative contribution. They are an indicator of what peers have given each other.
    Credits can be used in many ways depending on how your organization or project runs. They can represent actual money in which case this is an accounts payable page, or they could represent relative contribution in which case this is a contribution tracking page. To learn more about how credits can work, click here." help_section_my_account: "This is your account page. Unlike your public profile this page is private to you.

    Here you can do important things like change your password, email settings and other preferences. You can also see your current usage levels and if needed you can change your subscription plan with BetterMeans.

    If you have questions about your account, please email billing@bettermeans.com" help_section_welcome_index: "Welcome on board!|Get started by creating your own workstream, or browsing around and contributing to existing ones

    We highly recommend this overview of how things work on bettermeans:



    You can also visit our help site for a more info and answers to frequently asked questions.

    Please don't forget to send as all the feedback you have!


    " help_section_project_map: "This is the map" help_section_project_settings: "Use settings sparingly. If you're admin and core team, make sure to use core team tools when possible in order for things to stay democratic." help_section_motion_new: "Motions are used to make large important decisions that are project-wide. Notice the dropdown menu with the different types of motions. This is a democratic mechanism to make decisions together. To learn more about motions click here" help_section_project_new: "A Workstream is our lingo for a group or set of work.

    A workstream could be a department of your organization or it could be a specific project that you are working on by your self or with others. In creating a workstreams there are a few important things to pay attention to:

    • Public: A public workstream means that anyone can see your work and can comment and contribute. If you do not want to have this transparency with the community, you can uncheck this box and you are then creating a private workstream. As the creator, you control who has access, but also note that you have to pay for private workstreams. Read more about tradeoffs.
    • Volunteer: This is a signal to everyone working with you that all work in this workstream is fully volunteer.
    • Credits: Credits are a more open and democratic way of valuing work and awarding compensation. We are really excited about offering this as an option and we strongly encourage you to learn more about credits.
    Still have questions, get in touch

    " help_section_invitation_new: "Use invitations to enroll team members to join your workstream.

    When you send someone an invitation, they will be prompted to create a bettermeans account (if they don't have one already), and automatically added as a contributor to your workstream.

    You can only invite people to a root workstream. Their membership in a workstream applies to all sub work streams as well. In other words, a person can't belong to a sub workstream, and not belong to its parent workstream.

    Learn more in our help section.


    " help_section_browse_workstreams: "Welcome to the BetterMeans Community. Unlike your home page, this page is the overview of the whole BetterMeans platform. You can look around at other public workstreams and comment and join other people's projects. Learn more about the BetterMeans platform" help_section_retrospective_show: "This is a retrospective. In a retrospective compensation is awarded by your peers and your own self-assessment. This is an opportunity to be fairly compensated for your contribution.
    You want to look at all of the completed items and then give your best assessment of what percentage you think each person contributed. You also have a chance to say how sure you are using the slider at the bottom. For more information about retrospectives, click here." help_section_free: Keep your work open and inclusive, and we won't charge you a dime no matter how large you grow. ================================================ FILE: config/preinitializer.rb ================================================ begin require "rubygems" require "bundler" rescue LoadError raise "Could not load the bundler gem. Install it with `gem install bundler`." end if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") raise RuntimeError, "Your bundler version is too old for Rails 2.3." + "Run `gem install bundler` to upgrade." end begin # Set up load paths for all bundled gems ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) Bundler.setup rescue Bundler::GemNotFound raise RuntimeError, "Bundler couldn't find some gems." + "Did you run `bundle install`?" end ================================================ FILE: config/rails_best_practices.yml ================================================ AddModelVirtualAttributeCheck: { } AlwaysAddDbIndexCheck: { } DryBundlerInCapistranoCheck: { } IsolateSeedDataCheck: { } KeepFindersOnTheirOwnModelCheck: { } LawOfDemeterCheck: { } LongLineCheck: { max_line_length: 80 } MoveCodeIntoControllerCheck: { } MoveCodeIntoHelperCheck: { array_count: 3 } MoveCodeIntoModelCheck: { use_count: 2 } MoveFinderToNamedScopeCheck: { } MoveModelLogicIntoModelCheck: { use_count: 4 } NeedlessDeepNestingCheck: { nested_count: 2 } NotUseDefaultRouteCheck: { } NotUseTimeAgoInWordsCheck: { } OveruseRouteCustomizationsCheck: { customize_count: 3 } ProtectMassAssignmentCheck: { } RemoveEmptyHelpersCheck: { } RemoveTabCheck: { } RemoveTrailingWhitespaceCheck: { } RemoveUnusedMethodsInControllersCheck: { except_methods: [] } RemoveUnusedMethodsInHelpersCheck: { except_methods: [] } RemoveUnusedMethodsInModelsCheck: { except_methods: [] } ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 } ReplaceInstanceVariableWithLocalVariableCheck: { } RestrictAutoGeneratedRoutesCheck: { } SimplifyRenderInControllersCheck: { } SimplifyRenderInViewsCheck: { } UseBeforeFilterCheck: { customize_count: 2 } UseModelAssociationCheck: { } UseMultipartAlternativeAsContentTypeOfEmailCheck: { } UseParenthesesInMethodDefCheck: { } UseObserverCheck: { } UseQueryAttributeCheck: { } UseSayWithTimeInMigrationsCheck: { } UseScopeAccessCheck: { } ================================================ FILE: config/routes.rb ================================================ ActionController::Routing::Routes.draw do |map| map.resources :help_sections map.resources :reputations map.resources :credit_distributions map.resources :quotes map.signin 'login', :controller => 'account', :action => 'login' map.signout 'logout', :controller => 'account', :action => 'logout' map.connect 'accounts/rpx_token',:controller => 'account', :action => 'rpx_token' # TODO: remove this route, seems to no longer be used map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' map.connect 'help/:ctrl/:page', :controller => 'help' #What's this? map.connect 'help/:id', :controller => 'help', :action => 'show' map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog' map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog' map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog' map.with_options :controller => 'timelog' do |timelog| timelog.connect 'projects/:project_id/time_entries', :action => 'details' timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details| time_details.connect 'time_entries' time_details.connect 'issues/:issue_id/time_entries' time_details.connect 'projects/:project_id/issues/:issue_id/time_entries' end timelog.connect 'projects/:project_id/time_entries/report', :action => 'report' timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report| time_report.connect 'time_entries/report' end timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit| time_edit.connect 'issues/:issue_id/time_entries/new' end timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post} end map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post} map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get} map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post} map.with_options :controller => 'wiki' do |wiki_routes| wiki_routes.with_options :conditions => {:method => :get} do |wiki_views| wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit' wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename' wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history' wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff' wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate' end wiki_routes.connect 'projects/:id/wiki/:page/:action', :action => /edit|rename|destroy|preview|protect/, :conditions => {:method => :post} end map.with_options :controller => 'messages' do |messages_routes| messages_routes.with_options :conditions => {:method => :get} do |messages_views| messages_views.connect 'boards/:board_id/topics/new', :action => 'new' messages_views.connect 'boards/:board_id/topics/:id', :action => 'show' messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit' end messages_routes.with_options :conditions => {:method => :post} do |messages_actions| messages_actions.connect 'boards/:board_id/topics/new', :action => 'new' messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply' messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/ end end map.with_options :controller => 'invitations' do |invitations_routes| invitations_routes.with_options :conditions => {:method => :get} do |invitations_views| invitations_views.connect 'invitations/:id', :action => 'accept' end invitations_routes.with_options :conditions => {:method => :post} do |invitations_actions| invitations_actions.connect 'projects/:project_id/invitations/:id/:action', :action => /destroy|resend/ end end map.with_options :controller => 'email_updates' do |email_updates_routes| email_updates_routes.with_options :conditions => {:method => :get} do |invitations_views| email_updates_routes.connect 'email_updates/activate', :action => 'activate' end end map.resources :email_updates map.with_options :controller => 'boards' do |board_routes| board_routes.with_options :conditions => {:method => :get} do |board_views| board_views.connect 'projects/:project_id/boards', :action => 'index' board_views.connect 'projects/:project_id/boards/new', :action => 'new' board_views.connect 'projects/:project_id/boards/:id', :action => 'show' board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit' end board_routes.with_options :conditions => {:method => :post} do |board_actions| board_actions.connect 'projects/:project_id/boards', :action => 'new' board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/ end end map.with_options :controller => 'documents' do |document_routes| document_routes.with_options :conditions => {:method => :get} do |document_views| document_views.connect 'projects/:project_id/documents', :action => 'index' document_views.connect 'projects/:project_id/documents/new', :action => 'new' document_views.connect 'documents/:id', :action => 'show' document_views.connect 'documents/:id/edit', :action => 'edit' end document_routes.with_options :conditions => {:method => :post} do |document_actions| document_actions.connect 'projects/:project_id/documents', :action => 'new' document_actions.connect 'documents/:id/:action', :action => /destroy|edit/ end end map.with_options :controller => 'issues' do |issues_routes| issues_routes.with_options :conditions => {:method => :get} do |issues_views| issues_views.connect 'issues', :action => 'index' issues_views.connect 'issues/datadump.:format', :action => 'datadump' issues_views.connect 'issues.:format', :action => 'index' issues_views.connect 'projects/:project_id/issues', :action => 'index' issues_views.connect 'projects/:project_id/issues.:format', :action => 'index' issues_views.connect 'projects/:project_id/issues/new', :action => 'new' issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt' issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar' issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new' issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/ issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/ issues_views.connect 'issues/:id/show', :action => 'show', :id => /\d+/ end issues_routes.with_options :conditions => {:method => :post} do |issues_actions| issues_actions.connect 'projects/:project_id/issues', :action => 'new' issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/ # BUGBUG: :disagree and :reject don't seem to be used anymore issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy|start|finish|release|cancel|restart|prioritize|agree|disagree|estimate|accept|reject|join|leave|add_team_member|update_tags/, :id => /\d+/ issues_actions.connect 'issues/:container_id/attachments/create', :controller => 'attachments', :action => 'create' end end map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations| relations.connect 'issues/:issue_id/relations/:id', :action => 'new' relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy' end map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports| reports.connect 'projects/:id/issues/report' reports.connect 'projects/:id/issues/report/:detail' end map.with_options :controller => 'news' do |news_routes| news_routes.with_options :conditions => {:method => :get} do |news_views| news_views.connect 'news', :action => 'index' news_views.connect 'projects/:project_id/news', :action => 'index' news_views.connect 'projects/:project_id/news.:format', :action => 'index' news_views.connect 'news.:format', :action => 'index' news_views.connect 'projects/:project_id/news/new', :action => 'new' news_views.connect 'news/:id', :action => 'show' news_views.connect 'news/:id/edit', :action => 'edit' end # BUGBUG: this should probably be `:conditions => { :method => :post }` news_routes.with_options do |news_actions| news_actions.connect 'projects/:project_id/news', :action => 'new' news_actions.connect 'news/:id/edit', :action => 'edit' news_actions.connect 'news/:id/destroy', :action => 'destroy' end end map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new' map.resources :users do |users| users.resources :mails, :collection => { :delete_selected => :post } end map.with_options :controller => 'users' do |users| users.with_options :conditions => {:method => :get} do |user_views| user_views.connect 'users', :action => 'index' user_views.connect 'users/:id', :action => 'show', :id => /\d+/ user_views.connect 'users/new', :action => 'add' user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil end users.with_options :conditions => {:method => :post} do |user_actions| user_actions.connect 'users', :action => 'add' user_actions.connect 'users/new', :action => 'add' user_actions.connect 'users/:id/edit', :action => 'edit' user_actions.connect 'users/:id/memberships', :action => 'edit_membership' user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership' user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership' end end map.with_options :controller => 'retros' do |retro_routes| retro_routes.with_options :conditions => {:method => :get} do |retro_views| retro_views.connect 'projects/:project_id/retros', :action => 'index' retro_views.connect 'projects/:project_id/retros/new', :action => 'new' retro_views.connect 'projects/:project_id/retros/:action', :action => /index_json/ retro_views.connect 'projects/:project_id/retros/:id/:action', :action => /show|dashdata/ retro_views.connect 'projects/:project_id/retros/:id.:format', :action => 'show' retro_views.connect 'projects/:project_id/retros/:id/edit', :action => 'edit' end retro_routes.with_options :conditions => {:method => :post} do |retro_actions| retro_actions.connect 'projects/:project_id/retros', :action => 'new' retro_actions.connect 'projects/:project_id/retros/:id/:action', :action => /edit|destroy/ end end map.with_options :controller => 'projects' do |projects| projects.with_options :conditions => {:method => :get} do |project_views| project_views.connect 'projects', :action => 'index' project_views.connect 'projects.:format', :action => 'index' project_views.connect 'projects/new', :action => 'add' project_views.connect 'projects/index_latest', :action => 'index_latest' project_views.connect 'projects/index_active', :action => 'index_active' project_views.connect 'projects/update_scale', :action => 'update_scale' project_views.connect 'projects/:id', :action => 'overview' project_views.connect 'projects/:id/show', :action => 'overview' project_views.connect 'projects/:id/overview', :action => 'overview' # TODO: remove unused: :roadmap, :wiki, :changelog, :leave_core_team, # :core_vote, :agree, :disagree, :accept, :reject, :shares, # :reset_invitation_code project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings|team|wiki|join_core_team|leave_core_team|core_vote|dashdata|new_dashdata|dashboard|mypris|agree|disagree|accept|reject|credits|shares|community_members|community_members_array|issue_search|hourly_types|map|join|overview|reset_invitation_code|all_tags/ project_views.connect 'projects/:id/files', :action => 'list_files' project_views.connect 'projects/:id/files/new', :action => 'add_file' project_views.connect 'projects/:id/settings/:tab', :action => 'settings' project_views.connect 'issues/:show_issue_id', :action => 'dashboard' end projects.with_options :conditions => {:method => :post} do |project_actions| project_actions.connect 'projects/new', :action => 'add' project_actions.connect 'projects', :action => 'add' project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive|edit/ # TODO: remove this route project_actions.connect 'projects/:id/wiki', :action => 'wiki' project_actions.connect 'projects/:id/files/new', :action => 'add_file' project_actions.connect 'projects/:id/activities/save', :action => 'save_activities' end projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity| activity.connect 'projects/:id/activity' activity.connect 'activity', :id => nil end end map.with_options :controller => 'hourly_types' do |hourly_type_routes| hourly_type_routes.with_options :conditions => {:method => :get} do |hourly_type_views| hourly_type_views.connect 'projects/:project_id/hourly_types/new', :action => 'new' hourly_type_views.connect 'projects/:project_id/hourly_types/:id/edit', :action => 'edit' end hourly_type_routes.with_options :conditions => {:method => :post} do |hourly_type_action| hourly_type_action.connect 'projects/:project_id/hourly_types/new', :action => 'new' hourly_type_action.connect 'projects/:project_id/hourly_types/:id/:action', :action => /new|edit|destroy/ end end map.with_options :controller => 'recurly_notifications' do |recurly_routes| recurly_routes.with_options :conditions => {:method => :post} do |recurly_action| recurly_action.connect 'recurly_notifications/listen', :action => 'listen' end end map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/ map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/ map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/ map.resources :groups map.resources :activity_streams map.resources :projects, :has_many => :shares map.resources :projects, :has_many => :credits map.resources :projects, :has_many => :motions map.resources :projects, :has_many => :invitations #left old routes at the bottom for backwards compat map.connect 'projects/:project_id/issues/:action', :controller => 'issues' map.connect 'projects/:project_id/documents/:action', :controller => 'documents' map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards' map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages' map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki' map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations' map.connect 'projects/:project_id/news/:action', :controller => 'news' map.connect 'projects/:project_id/motions/:action', :controller => 'motions' map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/ #semi-static pages map.root :controller => 'home' map.home '', :controller => 'home', :action => 'index' map.static '/front/:page', :controller => 'home', :action => 'show', :page => /index.html|about.html|howitworks.html|contact.html|hq.html|pricing.html|signup.html|apps.html|products.html|services.html|single.html|tour.html|webdesign.html|index.htm|elements.html|privacy.html|library.html|testimonials.html|irb.html|open_enterprise_governance_model.html|user_agreement.html|why.html|how.html|what.html|inviteonly.html/ # Install the default route as the lowest priority. # TODO: remove these default routes once integration specs are complete map.connect ':controller/:action/:id' # BUGBUG: everything below the default routes above will be eclipsed by them map.connect 'robots.txt', :controller => 'welcome', :action => 'robots' map.resources :pages, :only => :show map.resources :todos map.resources :issue_votes map.resources :credits map.resources :shares map.resources :enterprises map.resources :comments map.resources :retro_ratings map.resources :retros map.resources :notifications map.resources :issues map.resources :credit_transfers end ================================================ FILE: config/s3.yml ================================================ production: access_key_id: <%=ENV['s3_access_key_id'] %> secret_access_key: <%=ENV['s3_secret_access_key'] %> bucket: bettermeans_production cname_bucket: false development: access_key_id: <%=ENV['s3_access_key_id'] %> secret_access_key: <%=ENV['s3_secret_access_key'] %> bucket: bettermeans_development cname_bucket: false test: &TEST access_key_id: <%=ENV['s3_access_key_id'] %> secret_access_key: <%=ENV['s3_secret_access_key'] %> bucket: bettermeans_development cname_bucket: false cucumber: <<: *TEST selenium: <<: *TEST ================================================ FILE: config/selenium.yml ================================================ # Please read as our directions have changed: # Move this file to your rails apps config directory and rename it to selenium.yml in order to configure the plugin # # General settings # environments: - test # - development # Uncomment this line to enable in development environment. N.B. your development database will likely be altered/destroyed/abducted #selenium_path: 'c:\selenium' #path to selenium installation. only needed if you for some reason don't want to use the bundled version of selenium core # # rake test:acceptance settings # browsers: # Windows # firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe' # ie: 'c:\Program Files\Internet Explorer\iexplore.exe' # Mac OS X firefox: '/Applications/Firefox.app/Contents/MacOS/firefox-bin' safari: '/Applications/Safari.app/Contents/MacOS/Safari' #host: 'localhost' #port_start: 3000 #port_end: 3005 #base_url_path: '/' #max_browser_duration: 120 #multi_window: false #result_dir: 'c:\result' # the directory where the results will be stored after a test:acceptance run #fixtures_path: <%= "#{RAILS_ROOT}/spec/fixtures" %> #selenium_tests_path: <%= "#{RAILS_ROOT}/spec/selenium" %> ================================================ FILE: config/settings.yml ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# # DO NOT MODIFY THIS FILE !!! # Settings can be defined through the application in Admin -> Settings app_title: default: BetterMeans app_subtitle: default: Work re-invented welcome_text: default: 'Welcome to BetterMeans!' login_required: default: 0 self_registration: default: '1' lost_password: default: 1 password_min_length: format: int default: 6 attachment_max_size: format: int default: 51200 issues_export_limit: format: int default: 500 activity_days_default: format: int default: 5 per_page_options: default: '25,50,100' mail_from: default: BetterMeans mail_from_raw: default: administrator@bettermeans.com bcc_recipients: default: 1 plain_text_mail: default: 0 text_formatting: default: textile wiki_compression: default: "" default_language: default: en host_name: default: bettermeans.com protocol: default: http feeds_limit: format: int default: 25 # Maximum size of files that can be displayed # inline through the file viewer (in KB) mat file_max_size_displayed: format: int default: 512 diff_max_lines_displayed: format: int default: 1500 commit_ref_keywords: default: 'refs,references,IssueID' commit_fix_keywords: default: 'fixes,closes' commit_fix_status_id: format: int default: 0 commit_fix_done_ratio: default: 100 # autologin duration in days # 0 means autologin is disabled autologin: format: int default: 30 # date format date_format: default: '%m/%d/%Y' time_format: default: '%I:%M %p' user_format: default: :firstname_lastname format: symbol cross_project_issue_relations: default: 1 notified_events: serialized: true default: - issue_added - issue_updated - news_added - message_posted mail_handler_body_delimiters: default: administrator@bettermeans.com mail_handler_api_enabled: default: 1 mail_handler_api_key: default: gX7EBfIyc9orWdXzQ9vM issue_list_default_columns: serialized: true default: - tracker - status - subject - pri - updated_at display_subprojects_issues: default: 1 issue_done_ratio: default: 'issue_field' default_projects_public: default: 1 default_projects_modules: serialized: true default: - issue_tracking - documents - wiki - boards - motions # Role given to a non-admin user who creates a project new_project_user_role_id: format: int default: 7 sequential_project_identifiers: default: 1 # encoding used to convert commit logs to UTF-8 commit_logs_encoding: default: 'UTF-8' ui_theme: default: 'bettermeans' emails_footer: default: |- You have received this notification because you have either subscribed to it, or are involved in it. To change your notification preferences, please click here: http://bettermeans.com/my/account emails_footer_nospam: default: |- You are receiving this email via bettermeans.com We have zero tolerance for spam. Please forward this email to abuse@bettermeans.com if you feel you are recieving it in error. Thanks! gravatar_enabled: default: 1 openid: default: 0 forum_name: default: |- General Discussion forum_description: default: |- General Discussion for Workstream : gravatar_default: default: identicon start_of_week: default: '' rest_api_enabled: default: 0 enabled_scm: default: '' sys_api_enabled: default: true repositories_encodings: default: '' repository_log_display_limit: default: '' autofetch_changesets: default: true ================================================ FILE: cucumber.yml ================================================ default: --tags ~@selenium -r features/support/env.rb -r features/step_definitions features selenium: --tags @selenium -r features/support/env.rb -r features/support/selenium.rb -r features/step_definitions features ================================================ FILE: db/migrate/20110320055526_acts_as_taggable_on_migration.rb ================================================ class ActsAsTaggableOnMigration < ActiveRecord::Migration def self.up create_table :tags do |t| t.column :name, :string end create_table :taggings do |t| t.column :tag_id, :integer t.column :taggable_id, :integer t.column :tagger_id, :integer t.column :tagger_type, :string # You should make sure that the column created is # long enough to store the required class names. t.column :taggable_type, :string t.column :context, :string t.column :created_at, :datetime end add_index :taggings, :tag_id add_index :taggings, [:taggable_id, :taggable_type, :context] end def self.down drop_table :taggings drop_table :tags end end ================================================ FILE: db/migrate/20110329230314_add_projectid_to_taggable.rb ================================================ class AddProjectidToTaggable < ActiveRecord::Migration def self.up add_column :taggings, :project_id, :integer end def self.down remove_column :taggings, :project_id end end ================================================ FILE: db/migrate/20110330041648_add_tags_to_issue.rb ================================================ class AddTagsToIssue < ActiveRecord::Migration def self.up add_column :issues, :tags_copy, :string end def self.down remove_column :issues, :tags_copy end end ================================================ FILE: db/schema.rb ================================================ # This file is auto-generated from the current state of the database. Instead of editing this file, # please use the migrations feature of Active Record to incrementally modify your database, and # then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your database schema. If you need # to create the application database on another system, you should be using db:schema:load, not running # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended to check this file into your version control system. ActiveRecord::Schema.define(:version => 20110330041648) do create_table "activity_stream_preferences", :force => true do |t| t.string "activity" t.string "location" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end add_index "activity_stream_preferences", ["activity", "user_id"], :name => "activity_stream_preferences_idx" create_table "activity_stream_totals", :force => true do |t| t.string "activity" t.integer "object_id" t.string "object_type" t.float "total", :default => 0.0 t.datetime "created_at" t.datetime "updated_at" end add_index "activity_stream_totals", ["activity", "object_id", "object_type"], :name => "activity_stream_totals_idx" create_table "activity_streams", :force => true do |t| t.string "verb" t.string "activity" t.integer "actor_id" t.string "actor_type" t.string "actor_name_method" t.integer "count", :default => 1 t.integer "object_id" t.string "object_type" t.string "object_name_method" t.integer "indirect_object_id" t.string "indirect_object_type" t.string "indirect_object_name_method" t.string "indirect_object_phrase" t.integer "status", :default => 0 t.datetime "created_at" t.datetime "updated_at" t.integer "project_id", :default => 0 t.string "actor_name" t.string "object_name" t.text "object_description" t.string "indirect_object_name" t.text "indirect_object_description" t.string "tracker_name" t.string "project_name" t.string "actor_email" t.boolean "is_public", :default => false t.integer "hidden_from_user_id", :default => 0 end add_index "activity_streams", ["actor_id", "actor_type"], :name => "activity_streams_by_actor" add_index "activity_streams", ["indirect_object_id", "indirect_object_type"], :name => "activity_streams_by_indirect_object" add_index "activity_streams", ["object_id", "object_type"], :name => "activity_streams_by_object" create_table "attachments", :force => true do |t| t.integer "container_id", :default => 0, :null => false t.string "container_type", :limit => 30, :default => "", :null => false t.string "filename", :default => "", :null => false t.string "disk_filename", :default => "", :null => false t.integer "filesize", :default => 0, :null => false t.string "content_type", :default => "" t.string "digest", :limit => 40, :default => "", :null => false t.integer "downloads", :default => 0, :null => false t.integer "author_id", :default => 0, :null => false t.datetime "created_at" t.string "description" end add_index "attachments", ["author_id"], :name => "index_attachments_on_author_id" add_index "attachments", ["container_id", "container_type"], :name => "index_attachments_on_container_id_and_container_type" add_index "attachments", ["created_at"], :name => "index_attachments_on_created_at" create_table "auth_sources", :force => true do |t| t.string "type", :limit => 30, :default => "", :null => false t.string "name", :limit => 60, :default => "", :null => false t.string "host", :limit => 60 t.integer "port" t.string "account" t.string "account_password", :limit => 60 t.string "base_dn" t.string "attr_login", :limit => 30 t.string "attr_firstname", :limit => 30 t.string "attr_lastname", :limit => 30 t.string "attr_mail", :limit => 30 t.boolean "onthefly_register", :default => false, :null => false t.boolean "tls", :default => false, :null => false end add_index "auth_sources", ["id", "type"], :name => "index_auth_sources_on_id_and_type" create_table "boards", :force => true do |t| t.integer "project_id", :null => false t.string "name", :default => "", :null => false t.string "description" t.integer "position", :default => 1 t.integer "topics_count", :default => 0, :null => false t.integer "messages_count", :default => 0, :null => false t.integer "last_message_id" end add_index "boards", ["last_message_id"], :name => "index_boards_on_last_message_id" add_index "boards", ["project_id"], :name => "boards_project_id" create_table "comments", :force => true do |t| t.string "commented_type", :limit => 30, :default => "", :null => false t.integer "commented_id", :default => 0, :null => false t.integer "author_id", :default => 0, :null => false t.text "comments" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end add_index "comments", ["author_id"], :name => "index_comments_on_author_id" add_index "comments", ["commented_id", "commented_type"], :name => "index_comments_on_commented_id_and_commented_type" create_table "credit_distributions", :force => true do |t| t.integer "user_id" t.integer "project_id" t.integer "retro_id" t.float "amount" t.datetime "created_at" t.datetime "updated_at" end create_table "credit_transfers", :force => true do |t| t.integer "sender_id" t.integer "recipient_id" t.integer "project_id" t.float "amount" t.string "note" t.datetime "created_at" t.datetime "updated_at" end create_table "credits", :force => true do |t| t.float "amount", :null => false t.datetime "issued_on" t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" t.integer "project_id" t.datetime "settled_on" t.boolean "enabled", :default => true end add_index "credits", ["owner_id"], :name => "index_credits_on_owner_id" add_index "credits", ["project_id"], :name => "index_credits_on_project_id" create_table "daily_digests", :force => true do |t| t.integer "issue_id" t.integer "journal_id" t.string "mail" t.datetime "created_at" t.datetime "updated_at" end create_table "delayed_jobs", :force => true do |t| t.integer "priority", :default => 0 t.integer "attempts", :default => 0 t.text "handler" t.text "last_error" t.datetime "run_at" t.datetime "locked_at" t.datetime "failed_at" t.string "locked_by" t.datetime "created_at" t.datetime "updated_at" end create_table "documents", :force => true do |t| t.integer "project_id", :default => 0, :null => false t.string "title", :limit => 60, :default => "", :null => false t.text "description" t.datetime "created_at" end add_index "documents", ["created_at"], :name => "index_documents_on_created_on" add_index "documents", ["project_id"], :name => "documents_project_id" create_table "email_updates", :force => true do |t| t.integer "user_id" t.string "mail" t.string "token" t.boolean "activated", :default => false t.datetime "created_at" t.datetime "updated_at" end create_table "enabled_modules", :force => true do |t| t.integer "project_id" t.string "name", :null => false end add_index "enabled_modules", ["project_id"], :name => "enabled_modules_project_id" create_table "enterprises", :force => true do |t| t.string "name" t.text "description" t.string "homepage", :default => "" t.datetime "created_at" t.datetime "updated_at" end create_table "enumerations", :force => true do |t| t.string "opt", :limit => 4, :default => "", :null => false t.string "name", :limit => 30, :default => "", :null => false t.integer "position", :default => 1 t.boolean "is_default", :default => false, :null => false t.string "type" t.boolean "active", :default => true, :null => false t.integer "project_id" t.integer "parent_id" end add_index "enumerations", ["id", "type"], :name => "index_enumerations_on_id_and_type" add_index "enumerations", ["project_id"], :name => "index_enumerations_on_project_id" create_table "help_sections", :force => true do |t| t.integer "user_id", :default => 0, :null => false t.string "name" t.boolean "show", :default => true t.datetime "created_at" t.datetime "updated_at" end create_table "hourly_types", :force => true do |t| t.integer "project_id" t.string "name" t.decimal "hourly_rate_per_person", :precision => 8, :scale => 2 t.decimal "hourly_cap", :precision => 8, :scale => 2 t.datetime "created_at" t.datetime "updated_at" end create_table "invitations", :force => true do |t| t.integer "user_id" t.integer "project_id" t.string "token" t.integer "status", :default => 0 t.integer "role_id" t.string "mail" t.datetime "created_at" t.datetime "updated_at" t.string "new_mail" end create_table "issue_relations", :force => true do |t| t.integer "issue_from_id", :null => false t.integer "issue_to_id", :null => false t.string "relation_type", :default => "", :null => false t.integer "delay" end add_index "issue_relations", ["issue_from_id"], :name => "index_issue_relations_on_issue_from_id" add_index "issue_relations", ["issue_to_id"], :name => "index_issue_relations_on_issue_to_id" create_table "issue_statuses", :force => true do |t| t.string "name", :limit => 30, :default => "", :null => false t.boolean "is_closed", :default => false, :null => false t.boolean "is_default", :default => false, :null => false t.integer "position", :default => 1 t.integer "default_done_ratio" end add_index "issue_statuses", ["is_closed"], :name => "index_issue_statuses_on_is_closed" add_index "issue_statuses", ["is_default"], :name => "index_issue_statuses_on_is_default" add_index "issue_statuses", ["position"], :name => "index_issue_statuses_on_position" create_table "issue_votes", :force => true do |t| t.float "points", :null => false t.integer "user_id", :null => false t.integer "issue_id", :null => false t.integer "vote_type", :null => false t.datetime "created_at" t.datetime "updated_at" t.boolean "isbinding", :default => false end add_index "issue_votes", ["issue_id"], :name => "index_issue_votes_on_issue_id" add_index "issue_votes", ["user_id"], :name => "index_issue_votes_on_user_id" add_index "issue_votes", ["vote_type"], :name => "index_issue_votes_on_vote_type" create_table "issues", :force => true do |t| t.integer "tracker_id", :default => 0, :null => false t.integer "project_id", :default => 0, :null => false t.string "subject", :default => "", :null => false t.text "description" t.date "due_date" t.integer "status_id", :default => 0, :null => false t.integer "assigned_to_id" t.integer "author_id", :default => 0, :null => false t.integer "lock_version", :default => 0, :null => false t.datetime "created_at" t.datetime "updated_at" t.date "start_date" t.integer "done_ratio", :default => 0, :null => false t.float "estimated_hours" t.date "expected_date" t.float "points" t.integer "pri", :default => 0 t.integer "accept", :default => 0 t.integer "reject", :default => 0 t.integer "accept_total", :default => 0 t.integer "agree", :default => 0 t.integer "disagree", :default => 0 t.integer "agree_total", :default => 0 t.integer "retro_id" t.integer "accept_nonbind", :default => 0 t.integer "reject_nonbind", :default => 0 t.integer "accept_total_nonbind", :default => 0 t.integer "agree_nonbind", :default => 0 t.integer "disagree_nonbind", :default => 0 t.integer "agree_total_nonbind", :default => 0 t.integer "points_nonbind", :default => 0 t.integer "pri_nonbind", :default => 0 t.integer "hourly_type_id" t.integer "num_hours", :default => 0 t.string "tags_copy" end add_index "issues", ["assigned_to_id"], :name => "index_issues_on_assigned_to_id" add_index "issues", ["author_id"], :name => "index_issues_on_author_id" add_index "issues", ["created_at"], :name => "index_issues_on_created_at" add_index "issues", ["project_id"], :name => "issues_project_id" add_index "issues", ["status_id"], :name => "index_issues_on_status_id" add_index "issues", ["tracker_id"], :name => "index_issues_on_tracker_id" create_table "journal_details", :force => true do |t| t.integer "journal_id", :default => 0, :null => false t.string "property", :limit => 30, :default => "", :null => false t.string "prop_key", :limit => 30, :default => "", :null => false t.string "old_value" t.string "value" end add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" create_table "journals", :force => true do |t| t.integer "journalized_id", :default => 0, :null => false t.string "journalized_type", :limit => 30, :default => "", :null => false t.integer "user_id", :default => 0, :null => false t.text "notes" t.datetime "created_at", :null => false t.datetime "updated_at" end add_index "journals", ["created_at"], :name => "index_journals_on_created_at" add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id" add_index "journals", ["journalized_id"], :name => "index_journals_on_journalized_id" add_index "journals", ["user_id"], :name => "index_journals_on_user_id" create_table "mail_handlers", :force => true do |t| t.datetime "created_at" t.datetime "updated_at" end create_table "mails", :force => true do |t| t.integer "sender_id" t.integer "recipient_id" t.boolean "sender_deleted", :default => false t.boolean "recipient_deleted", :default => false t.string "subject" t.text "body" t.datetime "read_at" t.datetime "created_at" t.datetime "updated_at" end create_table "member_roles", :force => true do |t| t.integer "member_id" t.integer "role_id" t.integer "inherited_from" t.datetime "created_at" t.datetime "updated_at" end add_index "member_roles", ["member_id"], :name => "index_member_roles_on_member_id" add_index "member_roles", ["role_id"], :name => "index_member_roles_on_role_id" create_table "members", :force => true do |t| t.integer "user_id", :default => 0, :null => false t.integer "project_id", :default => 0, :null => false t.datetime "created_at" t.boolean "mail_notification", :default => false, :null => false end add_index "members", ["project_id"], :name => "index_members_on_project_id" add_index "members", ["user_id"], :name => "index_members_on_user_id" create_table "messages", :force => true do |t| t.integer "board_id", :null => false t.integer "parent_id" t.string "subject", :default => "", :null => false t.text "content" t.integer "author_id" t.integer "replies_count", :default => 0, :null => false t.integer "last_reply_id" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.boolean "locked", :default => false t.integer "sticky", :default => 0 end add_index "messages", ["author_id"], :name => "index_messages_on_author_id" add_index "messages", ["board_id"], :name => "messages_board_id" add_index "messages", ["created_at"], :name => "index_messages_on_created_at" add_index "messages", ["last_reply_id"], :name => "index_messages_on_last_reply_id" add_index "messages", ["parent_id"], :name => "messages_parent_id" create_table "motion_votes", :force => true do |t| t.integer "motion_id" t.integer "user_id" t.integer "points" t.boolean "isbinding", :default => false t.datetime "created_at" t.datetime "updated_at" end create_table "motions", :force => true do |t| t.integer "project_id" t.string "title" t.text "description" t.text "params" t.integer "variation", :default => 0 t.integer "motion_type", :default => 2 t.integer "visibility_level", :default => 5 t.integer "binding_level", :default => 5 t.integer "state", :default => 0 t.datetime "created_at" t.datetime "updated_at" t.date "ends_on" t.integer "topic_id" t.integer "author_id" t.integer "agree", :default => 0 t.integer "disagree", :default => 0 t.integer "agree_total", :default => 0 t.integer "agree_nonbind", :default => 0 t.integer "disagree_nonbind", :default => 0 t.integer "agree_total_nonbind", :default => 0 t.integer "concerned_user_id" end create_table "news", :force => true do |t| t.integer "project_id" t.string "title", :limit => 60, :default => "", :null => false t.string "summary", :default => "" t.text "description" t.integer "author_id", :default => 0, :null => false t.datetime "created_at" t.integer "comments_count", :default => 0, :null => false end add_index "news", ["author_id"], :name => "index_news_on_author_id" add_index "news", ["created_at"], :name => "index_news_on_created_at" add_index "news", ["project_id"], :name => "news_project_id" create_table "notifications", :force => true do |t| t.integer "recipient_id" t.string "variation" t.text "params" t.integer "state", :default => 0 t.integer "source_id" t.datetime "expiration" t.datetime "created_at" t.datetime "updated_at" t.integer "sender_id" t.string "source_type" end add_index "notifications", ["recipient_id"], :name => "index_notifications_on_recipient_id" create_table "open_id_authentication_associations", :force => true do |t| t.integer "issued" t.integer "lifetime" t.string "handle" t.string "assoc_type" t.binary "server_url" t.binary "secret" end create_table "open_id_authentication_nonces", :force => true do |t| t.integer "timestamp", :null => false t.string "server_url" t.string "salt", :null => false end create_table "personal_welcomes", :force => true do |t| t.integer "user_id" t.datetime "created_at" end create_table "plans", :force => true do |t| t.string "name" t.integer "code" t.text "description" t.float "amount" t.integer "storage_max" t.integer "contributor_max" t.integer "private_workstream_max" t.integer "public_workstream_max" t.datetime "created_at" t.datetime "updated_at" end create_table "plugin_schema_info", :id => false, :force => true do |t| t.string "plugin_name" t.integer "version" end create_table "projects", :force => true do |t| t.string "name", :limit => 50, :default => "", :null => false t.text "description" t.string "homepage", :default => "" t.boolean "is_public", :default => true, :null => false t.integer "parent_id" t.datetime "created_at" t.datetime "updated_at" t.string "identifier", :limit => 20 t.integer "status", :default => 1, :null => false t.integer "lft" t.integer "rgt" t.integer "enterprise_id" t.datetime "last_item_updated_on" t.float "dpp", :default => 100.0 t.text "activity_line" t.boolean "volunteer", :default => false t.integer "owner_id" t.float "storage", :default => 0.0 t.integer "issue_count", :default => 0 t.integer "activity_total" t.string "invitation_token" t.integer "issue_count_sub", :default => 0, :null => false t.datetime "last_item_sub_updated_on" end add_index "projects", ["enterprise_id"], :name => "index_projects_on_enterprise_id" add_index "projects", ["lft"], :name => "index_projects_on_lft" add_index "projects", ["rgt"], :name => "index_projects_on_rgt" create_table "projects_trackers", :id => false, :force => true do |t| t.integer "project_id", :default => 0, :null => false t.integer "tracker_id", :default => 0, :null => false end add_index "projects_trackers", ["project_id", "tracker_id"], :name => "projects_trackers_unique", :unique => true add_index "projects_trackers", ["project_id"], :name => "projects_trackers_project_id" create_table "queries", :force => true do |t| t.integer "project_id" t.string "name", :default => "", :null => false t.text "filters" t.integer "user_id", :default => 0, :null => false t.boolean "is_public", :default => false, :null => false t.text "column_names" t.text "sort_criteria" t.string "group_by" end add_index "queries", ["project_id"], :name => "index_queries_on_project_id" add_index "queries", ["user_id"], :name => "index_queries_on_user_id" create_table "quotes", :force => true do |t| t.integer "user_id" t.string "author" t.text "body" t.datetime "created_at" t.datetime "updated_at" end create_table "reportable_cache", :force => true do |t| t.string "model_name", :limit => 100, :null => false t.string "report_name", :limit => 100, :null => false t.string "grouping", :limit => 10, :null => false t.string "aggregation", :limit => 10, :null => false t.string "conditions", :limit => 100, :null => false t.float "value", :default => 0.0, :null => false t.datetime "reporting_period", :null => false t.datetime "created_at" t.datetime "updated_at" end add_index "reportable_cache", ["model_name", "report_name", "grouping", "aggregation", "conditions", "reporting_period"], :name => "name_model_grouping_aggregation_period", :unique => true add_index "reportable_cache", ["model_name", "report_name", "grouping", "aggregation", "conditions"], :name => "name_model_grouping_agregation" create_table "reputations", :force => true do |t| t.integer "user_id" t.integer "project_id" t.integer "reputation_type" t.float "value" t.string "params" t.datetime "created_at" t.datetime "updated_at" end create_table "retro_ratings", :force => true do |t| t.integer "rater_id" t.integer "ratee_id" t.float "score" t.integer "retro_id" t.datetime "created_at" t.datetime "updated_at" t.integer "confidence", :default => 100 end add_index "retro_ratings", ["ratee_id"], :name => "index_retro_ratings_on_ratee_id" add_index "retro_ratings", ["rater_id"], :name => "index_retro_ratings_on_rater_id" create_table "retros", :force => true do |t| t.integer "status_id" t.integer "project_id" t.datetime "from_date" t.datetime "to_date" t.datetime "created_at" t.datetime "updated_at" t.integer "total_points" end add_index "retros", ["project_id"], :name => "index_retros_on_project_id" create_table "roles", :force => true do |t| t.string "name", :limit => 30, :default => "", :null => false t.integer "position", :default => 1 t.boolean "assignable", :default => true t.integer "builtin", :default => 0, :null => false t.text "permissions" t.integer "level", :default => 3 end create_table "settings", :force => true do |t| t.string "name", :default => "", :null => false t.text "value" t.datetime "updated_at" end add_index "settings", ["name"], :name => "index_settings_on_name" create_table "shares", :force => true do |t| t.float "amount", :null => false t.datetime "expires" t.integer "variation", :default => 2, :null => false t.datetime "issued_on" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" t.integer "owner_id" end add_index "shares", ["owner_id"], :name => "index_shares_on_owner_id" add_index "shares", ["project_id"], :name => "index_shares_on_project_id" create_table "taggings", :force => true do |t| t.integer "tag_id" t.integer "taggable_id" t.integer "tagger_id" t.string "tagger_type" t.string "taggable_type" t.string "context" t.datetime "created_at" t.integer "project_id" end add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context" create_table "tags", :force => true do |t| t.string "name" end create_table "todos", :force => true do |t| t.string "subject" t.integer "author_id" t.integer "owner_id" t.integer "issue_id" t.datetime "completed_on" t.datetime "created_at" t.datetime "updated_at" t.string "owner_login" end add_index "todos", ["author_id"], :name => "index_todos_on_author_id" add_index "todos", ["owner_id"], :name => "index_todos_on_owner_id" create_table "tokens", :force => true do |t| t.integer "user_id", :default => 0, :null => false t.string "action", :limit => 30, :default => "", :null => false t.string "value", :limit => 40, :default => "", :null => false t.datetime "created_at", :null => false end add_index "tokens", ["user_id"], :name => "index_tokens_on_user_id" create_table "trackers", :force => true do |t| t.string "name", :limit => 30, :default => "", :null => false t.boolean "is_in_chlog", :default => false, :null => false t.integer "position", :default => 1 t.boolean "is_in_roadmap", :default => true, :null => false t.boolean "for_credits_module", :default => false end create_table "tracks", :force => true do |t| t.integer "user_id" t.integer "code" t.datetime "created_at" t.datetime "updated_at" t.string "ip" end create_table "user_preferences", :force => true do |t| t.integer "user_id", :default => 0, :null => false t.text "others" t.boolean "hide_mail", :default => true t.string "time_zone" t.boolean "active_only_jumps", :default => true end add_index "user_preferences", ["user_id"], :name => "index_user_preferences_on_user_id" create_table "users", :force => true do |t| t.string "login", :limit => 30, :default => "", :null => false t.string "hashed_password", :limit => 40, :default => "", :null => false t.string "firstname", :limit => 30, :default => "", :null => false t.string "lastname", :limit => 30, :default => "", :null => false t.string "mail", :limit => 60, :default => "", :null => false t.boolean "mail_notification", :default => true, :null => false t.boolean "admin", :default => false, :null => false t.integer "status", :default => 1, :null => false t.datetime "last_login_on" t.string "language", :limit => 5, :default => "" t.integer "auth_source_id" t.datetime "created_at" t.datetime "updated_at" t.string "type" t.string "identity_url" t.string "activity_stream_token" t.string "identifier" t.integer "plan_id", :default => 1 t.string "b_first_name" t.string "b_last_name" t.string "b_address1" t.string "b_zip" t.string "b_country" t.string "b_phone" t.string "b_ip_address" t.string "b_cc_last_four" t.string "b_cc_type" t.integer "b_cc_month" t.integer "b_cc_year" t.string "mail_hash" t.datetime "trial_expires_on" t.boolean "active_subscription", :default => false t.datetime "usage_over_at" t.datetime "trial_expired_at" end add_index "users", ["auth_source_id"], :name => "index_users_on_auth_source_id" add_index "users", ["id", "type"], :name => "index_users_on_id_and_type" create_table "votes", :force => true do |t| t.boolean "vote", :default => false t.integer "voteable_id", :null => false t.string "voteable_type", :null => false t.integer "voter_id" t.string "voter_type" t.datetime "created_at" t.datetime "updated_at" end add_index "votes", ["voteable_id", "voteable_type", "voter_id", "voter_type"], :name => "uniq_one_vote_only", :unique => true add_index "votes", ["voteable_id", "voteable_type"], :name => "fk_voteables" add_index "votes", ["voter_id", "voter_type"], :name => "fk_voters" create_table "watchers", :force => true do |t| t.string "watchable_type", :default => "", :null => false t.integer "watchable_id", :default => 0, :null => false t.integer "user_id" end add_index "watchers", ["user_id", "watchable_type"], :name => "watchers_user_id_type" add_index "watchers", ["user_id"], :name => "index_watchers_on_user_id" add_index "watchers", ["watchable_id", "watchable_type"], :name => "index_watchers_on_watchable_id_and_watchable_type" create_table "wiki_content_versions", :force => true do |t| t.integer "wiki_content_id", :null => false t.integer "page_id", :null => false t.integer "author_id" t.binary "data" t.string "compression", :limit => 6, :default => "" t.string "comments", :default => "" t.datetime "updated_at", :null => false t.integer "version", :null => false end add_index "wiki_content_versions", ["updated_at"], :name => "index_wiki_content_versions_on_updated_at" add_index "wiki_content_versions", ["wiki_content_id"], :name => "wiki_content_versions_wcid" create_table "wiki_contents", :force => true do |t| t.integer "page_id", :null => false t.integer "author_id" t.text "text" t.string "comments", :default => "" t.datetime "updated_at", :null => false t.integer "version", :null => false end add_index "wiki_contents", ["author_id"], :name => "index_wiki_contents_on_author_id" add_index "wiki_contents", ["page_id"], :name => "wiki_contents_page_id" create_table "wiki_pages", :force => true do |t| t.integer "wiki_id", :null => false t.string "title", :null => false t.datetime "created_at", :null => false t.boolean "protected", :default => false, :null => false t.integer "parent_id" end add_index "wiki_pages", ["parent_id"], :name => "index_wiki_pages_on_parent_id" add_index "wiki_pages", ["title", "wiki_id"], :name => "wiki_pages_wiki_id_title" add_index "wiki_pages", ["wiki_id"], :name => "index_wiki_pages_on_wiki_id" create_table "wiki_redirects", :force => true do |t| t.integer "wiki_id", :null => false t.string "title" t.string "redirects_to" t.datetime "created_at", :null => false end add_index "wiki_redirects", ["title", "wiki_id"], :name => "wiki_redirects_wiki_id_title" add_index "wiki_redirects", ["wiki_id"], :name => "index_wiki_redirects_on_wiki_id" create_table "wikis", :force => true do |t| t.integer "project_id", :null => false t.string "start_page", :null => false t.integer "status", :default => 1, :null => false end add_index "wikis", ["project_id"], :name => "wikis_project_id" create_table "workflows", :force => true do |t| t.integer "tracker_id", :default => 0, :null => false t.integer "old_status_id", :default => 0, :null => false t.integer "new_status_id", :default => 0, :null => false t.integer "role_id", :default => 0, :null => false end add_index "workflows", ["new_status_id"], :name => "index_workflows_on_new_status_id" add_index "workflows", ["old_status_id", "role_id", "tracker_id"], :name => "wkfs_role_tracker_old_status" add_index "workflows", ["old_status_id"], :name => "index_workflows_on_old_status_id" add_index "workflows", ["role_id"], :name => "index_workflows_on_role_id" end ================================================ FILE: db/seeds.rb ================================================ # Autogenerated by the db:seed:dump task # Do not hesitate to tweak this to your needs # TODO: remove 'is_' from columns and variables u = User.new(:firstname => "admin", :mail => "admin@bettermeans.com") u.password = 'adminadmin' u.password_confirmation = 'adminadmin' u.login = 'admin' u.save # create anonymous user User.anonymous Tracker.delete_all trackers = Tracker.create([ { :position => 1, :name => "Feature", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => false }, { :position => 2, :name => "Chore", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => false }, { :position => 3, :name => "Bug", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => false }, { :position => 4, :name => "Gift", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => true }, { :position => 5, :name => "Expense", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => true }, { :position => 6, :name => "Recurring", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => false }, { :position => 7, :name => "Hourly", :is_in_roadmap => true, :is_in_chlog => false, :for_credits_module => true } ]) IssueStatus.delete_all issue_statuses = IssueStatus.create([ { :position => 3, :name => "Open", :is_default => false, :default_done_ratio => nil, :is_closed => false }, { :position => 4, :name => "Committed", :is_default => false, :default_done_ratio => nil, :is_closed => false }, { :position => 7, :name => "Blocked", :is_default => false, :default_done_ratio => nil, :is_closed => false }, { :position => 5, :name => "Done", :is_default => false, :default_done_ratio => nil, :is_closed => true }, { :position => 6, :name => "Canceled", :is_default => false, :default_done_ratio => nil, :is_closed => true }, { :position => 1, :name => "New", :is_default => true, :default_done_ratio => nil, :is_closed => false }, { :position => 2, :name => "Estimate", :is_default => false, :default_done_ratio => nil, :is_closed => false }, { :position => 8, :name => "Archived", :is_default => false, :default_done_ratio => nil, :is_closed => true }, { :position => 9, :name => "Accepted", :is_default => false, :default_done_ratio => nil, :is_closed => true }, { :position => 10, :name => "Rejected", :is_default => false, :default_done_ratio => nil, :is_closed => false } ]) Enumeration.delete_all enumerations = Enumeration.create([ { :position => 1, :name => "", :project_id => nil, :is_default => false, :type => nil, :parent_id => nil, :opt => "", :active => true } ]) Role.delete_all roles = Role.create([ { :position => 5, :name => "Non member", :level => 0, :builtin => 1, :permissions => [ :add_project, :credits, :join_from_generic_invitation, :add_messages, :view_credits, :view_issues, :add_issues, :edit_issues, :add_issue_notes, :edit_own_issue_notes, :pull_commitment, :estimate_issues, :accept_issues, :start_issues, :view_gantt, :view_issue_watchers, :browse_motion, :vote_motion, :comment_news, :view_wiki_pages, :view_wiki_edits ], :assignable => true }, { :position => 99, :name => "Anonymous", :level => 0, :builtin => 2, :permissions => [ :view_documents, :view_files, :view_issues, :view_member_roles, :view_wiki_pages, :view_wiki_edits ], :assignable => true }, { :position => 0, :name => "Administrator", :level => 1, :builtin => 3, :permissions => [ :add_project, :add_subprojects, :edit_project, :select_project_modules, :manage_members, :credits, :send_invitations, :manage_invitations, :manage_boards, :add_messages, :edit_messages, :edit_own_messages, :delete_messages, :delete_own_messages, :view_credits, :manage_documents, :view_documents, :manage_files, :view_files, :view_issues, :add_issues, :edit_issues, :manage_issue_relations, :add_issue_notes, :edit_issue_notes, :edit_own_issue_notes, :move_issues, :delete_issues, :push_commitment, :pull_commitment, :view_commit_requests, :view_member_roles, :estimate_issues, :accept_issues, :start_issues, :manage_public_queries, :save_queries, :view_gantt, :view_calendar, :view_issue_watchers, :add_issue_watchers, :manage_motion, :browse_motion, :create_motion, :vote_motion, :manage_news, :comment_news, :manage_wiki, :rename_wiki_pages, :delete_wiki_pages, :view_wiki_pages, :view_wiki_edits, :edit_wiki_pages, :delete_wiki_pages_attachments, :protect_wiki_pages ], :assignable => true }, { :position => 2, :name => "Core Team", :level => 1, :builtin => 4, :permissions => [ :add_project, :add_subprojects, :send_invitations, :manage_invitations, :transfer_credits, :join_from_generic_invitation, :manage_boards, :add_messages, :edit_own_messages, :view_credits, :enable_disable_credits, :manage_documents, :view_documents, :manage_files, :view_files, :view_issues, :add_issues, :edit_issues, :manage_issue_relations, :add_issue_notes, :edit_issue_notes, :edit_own_issue_notes, :move_issues, :push_commitment, :pull_commitment, :view_commit_requests, :view_member_roles, :estimate_issues, :accept_issues, :start_issues, :save_queries, :view_gantt, :view_calendar, :view_issue_watchers, :add_issue_watchers, :manage_motion, :browse_motion, :create_motion, :vote_motion, :manage_news, :comment_news, :manage_wiki, :rename_wiki_pages, :delete_wiki_pages, :view_wiki_pages, :view_wiki_edits, :edit_wiki_pages, :delete_wiki_pages_attachments, :protect_wiki_pages ], :assignable => true }, { :position => 10, :name => "Active", :level => 2, :builtin => 7, :permissions => nil, :assignable => true }, { :position => 3, :name => "Member", :level => 1, :builtin => 8, :permissions => [ :add_project, :add_subprojects, :send_invitations, :manage_invitations, :transfer_credits, :join_from_generic_invitation, :add_messages, :edit_own_messages, :view_credits, :enable_disable_credits, :view_documents, :view_files, :view_issues, :add_issues, :edit_issues, :manage_issue_relations, :add_issue_notes, :edit_issue_notes, :edit_own_issue_notes, :push_commitment, :pull_commitment, :view_commit_requests, :view_member_roles, :estimate_issues, :accept_issues, :start_issues, :manage_public_queries, :save_queries, :view_gantt, :view_calendar, :view_issue_watchers, :browse_motion, :create_motion, :vote_motion, :comment_news, :rename_wiki_pages, :view_wiki_pages, :view_wiki_edits, :edit_wiki_pages ], :assignable => true }, { :position => 1, :name => "Board", :level => 1, :builtin => 9, :permissions => [ :add_project, :add_subprojects, :manage_members, :transfer_credits, :join_from_generic_invitation, :add_messages, :view_credits, :enable_disable_credits, :view_issues, :add_issues, :add_issue_notes, :edit_own_issue_notes, :pull_commitment, :start_issues, :view_gantt, :view_issue_watchers, :manage_motion, :browse_motion, :create_motion, :vote_motion, :comment_news, :view_wiki_pages, :view_wiki_edits, :edit_wiki_pages ], :assignable => true }, { :position => 11, :name => "Clearance", :level => 2, :builtin => 10, :permissions => nil, :assignable => true }, { :position => 4, :name => "Contributor", :level => 1, :builtin => 5, :permissions => [ :add_project, :transfer_credits, :join_from_generic_invitation, :add_messages, :view_credits, :enable_disable_credits, :view_documents, :view_files, :view_issues, :add_issues, :edit_issues, :add_issue_notes, :edit_own_issue_notes, :pull_commitment, :view_commit_requests, :estimate_issues, :accept_issues, :start_issues, :save_queries, :view_gantt, :view_issue_watchers, :browse_motion, :vote_motion, :comment_news, :view_wiki_pages, :view_wiki_edits, :edit_wiki_pages ], :assignable => true } ]) Plan.delete_all plans = Plan.create([ { :name => "Free", :created_at => nil, :code => 0, :updated_at => nil, :storage_max => 1, :contributor_max => 0, :amount => 0.0, :public_workstream_max => -1, :private_workstream_max => 0, :description => "Free plan" }, { :name => "Basic", :created_at => nil, :code => 1, :updated_at => nil, :storage_max => 1, :contributor_max => 5, :amount => 25.0, :public_workstream_max => -1, :private_workstream_max => 10, :description => "Best for small projects" }, { :name => "Better", :created_at => nil, :code => 2, :updated_at => nil, :storage_max => 5, :contributor_max => 20, :amount => 50.0, :public_workstream_max => -1, :private_workstream_max => 25, :description => "For medium-sized projects" }, { :name => "Super", :created_at => nil, :code => 3, :updated_at => nil, :storage_max => 50, :contributor_max => 100, :amount => 100.0, :public_workstream_max => -1, :private_workstream_max => 60, :description => "Our most popular plan" }, { :name => "Go Nuts!", :created_at => nil, :code => 4, :updated_at => nil, :storage_max => 100, :contributor_max => -1, :amount => 200.0, :public_workstream_max => -1, :private_workstream_max => -1, :description => "Why limit yourself?" } ]) Setting.delete_all settings = Setting.create([ { :name => "host_name", :updated_at => "Mon Aug 24 22:30:20 -0700 2009", :value => "rm.bettermeans.com" }, { :name => "date_format", :updated_at => "Mon Aug 24 20:28:44 -0700 2009", :value => "" }, { :name => "feeds_limit", :updated_at => "Mon Aug 24 20:28:44 -0700 2009", :value => "15" }, { :name => "default_language", :updated_at => "Mon Aug 24 20:28:44 -0700 2009", :value => "en" }, { :name => "welcome_text", :updated_at => "Thu May 20 12:33:28 -0700 2010", :value => "Welcome to BetterMeans! \r\n\r\nWe're currently running a " << "pre-beta release of our web platform.\r\nPlease report all " << "bugs in the web platform workstream of the bettermeans " << "enterprise" }, { :name => "diff_max_lines_displayed", :updated_at => "Mon Aug 24 20:28:44 -0700 2009", :value => "1500" }, { :name => "app_title", :updated_at => "Thu May 20 12:33:28 -0700 2010", :value => "BetterMeans" }, { :name => "activity_days_default", :updated_at => "Wed Jun 02 13:43:47 -0700 2010", :value => "5" }, { :name => "per_page_options", :updated_at => "Mon Aug 24 20:28:44 -0700 2009", :value => "25, 50, 100" }, { :name => "wiki_compression", :updated_at => "Thu Feb 11 20:49:21 -0800 2010", :value => "" }, { :name => "attachment_max_size", :updated_at => "Wed Oct 14 14:50:55 -0700 2009", :value => "51200" }, { :name => "time_format", :updated_at => "Mon Aug 24 20:28:45 -0700 2009", :value => "" }, { :name => "ui_theme", :updated_at => "Wed Feb 24 03:14:19 -0800 2010", :value => "bettermeans" }, { :name => "text_formatting", :updated_at => "Mon Aug 24 20:28:45 -0700 2009", :value => "textile" }, { :name => "user_format", :updated_at => "Mon Aug 24 20:28:45 -0700 2009", :value => "firstname_lastname" }, { :name => "gravatar_enabled", :updated_at => "Mon Aug 24 20:28:45 -0700 2009", :value => "1" }, { :name => "protocol", :updated_at => "Mon Aug 24 20:28:45 -0700 2009", :value => "http" }, { :name => "login_required", :updated_at => "Thu Jun 03 17:01:27 -0700 2010", :value => "0" }, { :name => "autologin", :updated_at => "Mon Aug 24 20:31:26 -0700 2009", :value => "30" }, { :name => "lost_password", :updated_at => "Mon Aug 24 20:31:26 -0700 2009", :value => "1" }, { :name => "self_registration", :updated_at => "Wed Jun 30 14:21:01 -0700 2010", :value => "3" }, { :name => "bcc_recipients", :updated_at => "Sun Jun 06 01:10:02 -0700 2010", :value => "1" }, { :name => "plain_text_mail", :updated_at => "Mon Aug 24 21:01:35 -0700 2009", :value => "0" }, { :name => "emails_footer", :updated_at => "Mon Aug 24 22:44:05 -0700 2009", :value => "You have received this notification because you have either " << "subscribed to it, or are involved in it.\r\nTo change your " << "notification preferences, please click here: " << "http://rm.bettermeans.com/my/account" }, { :name => "mail_from", :updated_at => "Sun Jun 06 01:14:45 -0700 2010", :value => "BetterMeans Admin" }, { :name => "notified_events", :updated_at => "Thu May 27 22:52:27 -0700 2010", :value => %w[ issue_added issue_updated news_added message_posted ].to_yaml }, { :name => "default_projects_public", :updated_at => "Tue Aug 25 00:27:38 -0700 2009", :value => "1" }, { :name => "sequential_project_identifiers", :updated_at => "Mon Aug 24 22:43:15 -0700 2009", :value => "1" }, { :name => "mail_handler_api_key", :updated_at => "Mon Aug 24 22:44:29 -0700 2009", :value => "gX7EBfIyc9orWdXzQ9vM" }, { :name => "mail_handler_api_enabled", :updated_at => "Mon Aug 24 22:44:29 -0700 2009", :value => "1" }, { :name => "display_subprojects_issues", :updated_at => "Tue Aug 25 00:17:45 -0700 2009", :value => "1" }, { :name => "issues_export_limit", :updated_at => "Tue Aug 25 00:17:45 -0700 2009", :value => "500" }, { :name => "cross_project_issue_relations", :updated_at => "Wed Oct 14 14:49:39 -0700 2009", :value => "1" }, { :name => "issue_list_default_columns", :updated_at => "Mon Dec 14 16:37:33 -0800 2009", :value => %w[ status subject assigned_to fixed_version ].to_yaml }, { :name => "new_project_user_role_id", :updated_at => "Tue Jun 08 15:20:43 -0700 2010", :value => "7" }, { :name => "file_max_size_displayed", :updated_at => "Wed Oct 14 14:50:55 -0700 2009", :value => "512" }, { :name => "openid", :updated_at => "Thu Feb 11 20:51:10 -0800 2010", :value => "0" }, { :name => "password_min_length", :updated_at => "Thu Jun 03 17:01:41 -0700 2010", :value => "6" }, { :name => "default_projects_modules", :updated_at => "Mon Dec 14 16:36:57 -0800 2009", :value => %w{ issue_tracking news documents files wiki boards }.to_yaml }, { :name => "rest_api_enabled", :updated_at => "Thu Feb 11 20:51:10 -0800 2010", :value => "0" }, { :name => "gravatar_default", :updated_at => "Thu Feb 11 20:52:56 -0800 2010", :value => "identicon" }, { :name => "start_of_week", :updated_at => "Thu Feb 11 20:52:56 -0800 2010", :value => "" }, { :name => "commit_fix_keywords", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "fixes,closes" }, { :name => "enabled_scm", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => %w[ Subversion Darcs Mercurial Cvs Bazaar Git ].to_yaml }, { :name => "commit_fix_status_id", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "0" }, { :name => "sys_api_enabled", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "0" }, { :name => "repositories_encodings", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "" }, { :name => "commit_fix_done_ratio", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "100" }, { :name => "commit_ref_keywords", :updated_at => "Thu Feb 11 20:53:31 -0800 2010", :value => "refs,references,IssueID" }, { :name => "repository_log_display_limit", :updated_at => "Thu Feb 11 20:53:32 -0800 2010", :value => "100" }, { :name => "commit_logs_encoding", :updated_at => "Thu Feb 11 20:53:32 -0800 2010", :value => "UTF-8" }, { :name => "autofetch_changesets", :updated_at => "Thu Feb 11 20:53:32 -0800 2010", :value => "1" }, { :name => "mail_handler_body_delimiters", :updated_at => "Fri May 28 02:09:56 -0700 2010", :value => "" } ]) ================================================ FILE: deploy ================================================ echo pushing to heroku... git push heroku echo pushing to github... git push origin master echo migrating db... heroku rake db:migrate echo backing up db after new code... heroku pgbackups heroku rake backup ================================================ FILE: doc/COPYING ================================================ Copyright (C) 2006-2009 Shereef Bishay BetterMeans All Rights Reserved ================================================ FILE: doc/INSTALL ================================================ == Redmine installation Redmine - project management software Copyright (C) 2006-2011 See readme for details and license http://www.redmine.org/ == Requirements * Ruby on Rails 2.2.2 * A database: * MySQL (tested with MySQL 5) * PostgreSQL (tested with PostgreSQL 8.1) * SQLite (tested with SQLite 3) Optional: * SVN binaries >= 1.3 (needed for repository browsing, must be available in PATH) * RMagick (gantt export to png) == Installation 1. Uncompress the program archive 2. Create an empty database: "redmine" for example 3. Configure database parameters in config/database.yml for "production" environment (default database is MySQL) 4. Create the database structure. Under the application main directory: rake db:migrate RAILS_ENV="production" It will create tables and an administrator account. 5. Generate a session store secret Redmine stores session data in cookies by default, which requires a secret to be generated. Run: rake config/initializers/session_store.rb 6. Setting up permissions The user who runs Redmine must have write permission on the following subdirectories: files, log, tmp (create the last one if not present). Assuming you run Redmine with a user named redmine: mkdir tmp sudo chown -R redmine:redmine files log tmp sudo chmod -R 755 files log tmp 7. Test the installation by running WEBrick web server: ruby script/server -e production Once WEBrick has started, point your browser to http://localhost:3000/ You should now see the application welcome page 8. Use default administrator account to log in: login: admin password: admin Go to "Administration" to load the default configuration data (roles, trackers, statuses, workflow) and adjust application settings == Email delivery Configuration Copy config/email.yml.example to config/email.yml and edit this file to adjust your SMTP settings. Don't forget to restart the application after any change to this file. Please do not enter your SMTP settings in environment.rb. ================================================ FILE: doc/README_FOR_APP ================================================ = BetterMeans BetterMeans is software that allows enterprises to run in a new decentralized way. More information at: http://bettermeans.com ================================================ FILE: doc/RUNNING_TESTS ================================================ Installing gems for testing =========================== Run `rake gems RAILS_ENV=test` to list the required gems. Run `rake gems:install RAILS_ENV=test` to install any missing gems. Running Tests ============= Run `rake --tasks test` to see available tests. `rake test` will run the entire testsuite. Before running `rake test` you need to configure both development and test databases. Creating test repositories =================== Redmine supports a wide array of different version control systems. To test the support, a test repository needs to be created for each of those. Run `rake --tasks test:scm:setup` for a list of available test-repositories or run `rake test:scm:setup:all` to set up all of them ================================================ FILE: extra/mail_handler/rdm-mailhandler.rb ================================================ #!/usr/bin/env ruby # == Synopsis # # Reads an email from standard input and forward it to a Redmine server # through a HTTP request. # # == Usage # # rdm-mailhandler [options] --url= --key= # # == Arguments # # -u, --url URL of the Redmine server # -k, --key Redmine API key # # General options: # --unknown-user=ACTION how to handle emails from an unknown user # ACTION can be one of the following values: # ignore: email is ignored (default) # accept: accept as anonymous user # create: create a user account # --no-permission-check disable permission checking when receiving # the email # -h, --help show this help # -v, --verbose show extra information # -V, --version show version information and exit # # Issue attributes control options: # -p, --project=PROJECT identifier of the target project # -s, --status=STATUS name of the target status # -t, --tracker=TRACKER name of the target tracker # --category=CATEGORY name of the target category # --priority=PRIORITY name of the target priority # -o, --allow-override=ATTRS allow email content to override attributes # specified by previous options # ATTRS is a comma separated list of attributes # # == Examples # No project specified. Emails MUST contain the 'Project' keyword: # # rdm-mailhandler --url http://redmine.domain.foo --key secret # # Fixed project and default tracker specified, but emails can override # both tracker and priority attributes using keywords: # # rdm-mailhandler --url https://domain.foo/redmine --key secret \\ # --project foo \\ # --tracker bug \\ # --allow-override tracker,priority require 'net/http' require 'net/https' require 'uri' require 'getoptlong' require 'rdoc/usage' module Net class HTTPS < HTTP def self.post_form(url, params) request = Post.new(url.path) request.form_data = params request.basic_auth url.user, url.password if url.user http = new(url.host, url.port) http.use_ssl = (url.scheme == 'https') http.start {|h| h.request(request) } end end end class RedmineMailHandler VERSION = '0.1' attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key def initialize self.issue_attributes = {} opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--version', '-V', GetoptLong::NO_ARGUMENT ], [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], [ '--status', '-s', GetoptLong::REQUIRED_ARGUMENT ], [ '--tracker', '-t', GetoptLong::REQUIRED_ARGUMENT], [ '--category', GetoptLong::REQUIRED_ARGUMENT], [ '--priority', GetoptLong::REQUIRED_ARGUMENT], [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT], [ '--unknown-user', GetoptLong::REQUIRED_ARGUMENT], [ '--no-permission-check', GetoptLong::NO_ARGUMENT] ) opts.each do |opt, arg| case opt when '--url' self.url = arg.dup when '--key' self.key = arg.dup when '--help' usage when '--verbose' self.verbose = true when '--version' puts VERSION; exit when '--project', '--status', '--tracker' self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup when '--allow-override' self.allow_override = arg.dup when '--unknown-user' self.unknown_user = arg.dup when '--no-permission-check' self.no_permission_check = '1' end end RDoc.usage if url.nil? end def submit(email) uri = url.gsub(%r{/*$}, '') + '/mail_handler' data = { 'key' => key, 'email' => email, 'allow_override' => allow_override, 'unknown_user' => unknown_user, 'no_permission_check' => no_permission_check} issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } debug "Posting to #{uri}..." response = Net::HTTPS.post_form(URI.parse(uri), data) debug "Response received: #{response.code}" case response.code.to_i when 403 warn "Request was denied by your Redmine server. " + "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key." return 77 when 422 warn "Request was denied by your Redmine server. " + "Possible reasons: email is sent from an invalid email address or is missing some information." return 77 when 400..499 warn "Request was denied by your Redmine server (#{response.code})." return 77 when 500..599 warn "Failed to contact your Redmine server (#{response.code})." return 75 when 201 debug "Proccessed successfully" return 0 else return 1 end end private def debug(msg) puts msg if verbose end end handler = RedmineMailHandler.new exit(handler.submit(STDIN.read)) ================================================ FILE: extra/sample_plugin/README ================================================ == Sample plugin This is a sample plugin for Redmine == Installation 1. Copy the plugin directory into the vendor/plugins directory 2. Migrate plugin: rake db:migrate_plugins 3. Start Redmine Installed plugins are listed and can be configured from 'Admin -> Plugins' screen. ================================================ FILE: extra/sample_plugin/app/controllers/example_controller.rb ================================================ # Sample plugin controller class ExampleController < ApplicationController unloadable layout 'base' before_filter :find_project, :authorize menu_item :sample_plugin def say_hello @value = Setting.plugin_sample_plugin['sample_setting'] end def say_goodbye end private def find_project @project=Project.find(params[:id]) end end ================================================ FILE: extra/sample_plugin/app/models/meeting.rb ================================================ class Meeting < ActiveRecord::Base belongs_to :project acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, :datetime => :scheduled_on, :author => nil, :url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} acts_as_activity_provider :timestamp => 'scheduled_on', :find_options => { :include => :project } end ================================================ FILE: extra/sample_plugin/app/views/example/say_goodbye.html.erb ================================================

    <%= l(:text_say_goodbye) %>

    <% content_for :header_tags do %> <%= stylesheet_link_tag "example.css", :plugin => "sample_plugin", :media => "screen" %> <% end %> ================================================ FILE: extra/sample_plugin/app/views/example/say_hello.html.erb ================================================

    <%= l(:text_say_hello) %>

    : <%= @value %>

    <%= link_to_if_authorized 'Good bye', :action => 'say_goodbye', :id => @project %> <% content_for :header_tags do %> <%= stylesheet_link_tag "example.css", :plugin => "sample_plugin", :media => "screen" %> <% end %> ================================================ FILE: extra/sample_plugin/app/views/my/blocks/_sample_block.html.erb ================================================

    Sample block

    You are <%= h(User.current) %> and this is a sample block for My Page added from a plugin. ================================================ FILE: extra/sample_plugin/app/views/settings/_sample_plugin_settings.html.erb ================================================

    <%= text_field_tag 'settings[sample_setting]', @settings['sample_setting'] %>

    <%= text_field_tag 'settings[foo]', @settings['foo'] %>

    ================================================ FILE: extra/sample_plugin/assets/stylesheets/example.css ================================================ .icon-example-works { background-image: url(../images/it_works.png); } ================================================ FILE: extra/sample_plugin/config/locales/en.yml ================================================ # Sample plugin en: label_plugin_example: Sample Plugin label_meeting_plural: Meetings text_say_hello: Plugin say 'Hello' text_say_goodbye: Plugin say 'Good bye' ================================================ FILE: extra/sample_plugin/config/locales/fr.yml ================================================ # Sample plugin fr: label_plugin_example: Plugin exemple label_meeting_plural: Meetings text_say_hello: Plugin dit 'Bonjour' text_say_goodbye: Plugin dit 'Au revoir' ================================================ FILE: extra/sample_plugin/db/migrate/001_create_meetings.rb ================================================ # Sample plugin migration # Use rake db:migrate_plugins to migrate installed plugins class CreateMeetings < ActiveRecord::Migration def self.up create_table :meetings do |t| t.column :project_id, :integer, :null => false t.column :description, :string t.column :scheduled_on, :datetime end end def self.down drop_table :meetings end end ================================================ FILE: extra/sample_plugin/init.rb ================================================ # Redmine sample plugin require 'redmine' RAILS_DEFAULT_LOGGER.info 'Starting Example plugin for RedMine' Redmine::Plugin.register :sample_plugin do name 'Example plugin' author 'Author name' description 'This is a sample plugin for Redmine' version '0.0.1' settings :default => {'sample_setting' => 'value', 'foo'=>'bar'}, :partial => 'settings/sample_plugin_settings' # This plugin adds a project module # It can be enabled/disabled at project level (Project settings -> Modules) project_module :example_module do # A public action permission :example_say_hello, {:example => [:say_hello]}, :public => true # This permission has to be explicitly given # It will be listed on the permissions screen permission :example_say_goodbye, {:example => [:say_goodbye]} # This permission can be given to project members only permission :view_meetings, {:meetings => [:index, :show]}, :require => :member end # A new item is added to the project menu menu :project_menu, :sample_plugin, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' # Meetings are added to the activity view activity_provider :meetings end ================================================ FILE: lib/activity_streams/log_activity_streams.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # The LogActivityStreams module adds a controller class method and # helper for automatically logging activity streams. # # README provides examples module LogActivityStreams def self.write_single_activity_stream(actor,actor_name,object,object_name,verb,activity, status, indirect_object, options) # If there are identical activities within 8 hours, up count as = find_identical(actor, object, verb, activity); if as && !(as.object_type.downcase == 'issue' && as.indirect_object_description != nil) #if action was found, and action is NOT a comment on an issue) as.count += 1 else as = ActivityStream.new as.verb = verb.to_s as.activity = activity.to_s as.actor = actor as.actor_name_method = actor_name.to_s as.actor_email = "<#{actor.mail}>" as.object = object as.object_name_method = object_name.to_s as.status = status as.project_id = options[:project_id] || object.send('project_id') as.project_name = Project.find(as.project_id).name #Pre-generating text as.actor_name = actor.send(actor_name) as.object_name = object.send(object_name) as.object_description = object.send(options[:object_description_method]) if options[:object_description_method] if as.object_type == "Issue" as.tracker_name = as.object.tracker.name #hiding gifts if as.object.tracker.gift? as.hidden_from_user_id = as.object.assigned_to_id as.is_public = false end end if indirect_object as.indirect_object = indirect_object as.indirect_object_name_method = options[:indirect_object_name_method].to_s as.indirect_object_phrase = options[:indirect_object_phrase] if options[:indirect_object_name_method] as.indirect_object_name = indirect_object.send(options[:indirect_object_name_method]) end if options[:indirect_object_description_method] as.indirect_object_description = indirect_object.send(options[:indirect_object_description_method]) end end end as.save! end def self.find_identical(actor, object, verb, activity) # :nodoc: return nil unless object.respond_to?(:project_id) ActivityStream.find(:first, :conditions => [ 'actor_id = ? AND actor_type = ? AND object_id = ? AND object_type = ? AND verb = ? AND activity = ? AND updated_at >= ? AND project_id = ? AND status = 0', actor.id, actor.class.name, object.id, object.class.name, verb.to_s, activity.to_s, Time.now - 8.hours, object.project_id]) end def self.included(controller) #:nodoc: controller.extend(ClassMethods) controller.helper_method :activity_stream_location end module ClassMethods #:nodoc: # log_activity_streams writes the activity stream from a controller. # # README provides examples of how to call log_activity_streams def log_activity_streams(actor_method, actor_name, verb, object_method, object_name, action, activity, options={}) self.after_filter do |c| c.send(:write_activity_stream_log, actor_method, actor_name, verb, object_method, object_name, action, activity, options) end end end protected # activity_stream_location is a helper method for determing the current 'location' (public, logged in users). # # Example: # <%= render :partial => 'activity_streams/activity_stream', :collection => ActivityStream.recent_actors(@user, activity_stream_location) %> # def activity_stream_location if not logged_in? :public_location else :logged_in_location end end def write_activity_stream_log(actor_method, actor_name, verb, object_method, object_name, action, activity, options={}) #:nodoc: return unless action == self.action_name.to_sym return if !flash.now[:error].blank? || @suppress_activity_stream status = options[:status] || 0 if actor_method.to_s.start_with?('@') actors = self.instance_variable_get(actor_method) || [] else actors = self.send(actor_method) || [] end actors = [ actors ] unless actors.is_a? Array return if actors.empty? || actors.first == :false if object_method.to_s.start_with?('@') objects = self.instance_variable_get(object_method) || [] else objects = self.send(object_method) || [] end objects = [ objects ] unless objects.is_a? Array if indirect_object_method = options[:indirect_object] if indirect_object_method.to_s.start_with?('@') indirect_object = self.instance_variable_get(indirect_object_method) else indirect_object = self.send(indirect_object_method) end end actors.each do |actor| objects.each do |object| # ensure no errors on object, as a validation error would mean no # activity should fire next unless object.errors.empty? LogActivityStreams.write_single_activity_stream(actor,actor_name,object,object_name,verb,activity,status,indirect_object, options) total = options[:total] if total total_for = options[:total_for] || :actor if total_for == :actor target = actor else target = object end if total.is_a? Symbol if total.to_s.start_with?('@') total = self.instance_variable_get(total) else total = self.send(total) || [] end end activity_stream_total = ActivityStreamTotal.find(:first, :conditions => { :activity => activity, :object_id => target.id, :object_type => target.class.name} ) || ActivityStreamTotal.new(:object => target, :activity => activity) activity_stream_total.total += total activity_stream_total.save! end end end end end ================================================ FILE: lib/activity_streams/routes.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # routes.rb adds additional routes for ActivityStreamsModule # # class ActionController::Routing::RouteSet # :nodoc: # def draw # :nodoc: # clear! # mapper = Mapper.new(self) # # activity_stream_maps(mapper) # # yield mapper # # install_helpers # end # # def activity_stream_maps(map) # :nodoc: # map.your_activities '/feeds/your_activities/:activity_stream_token', :controller => 'activity_streams', :action => 'feed', :format => 'atom' # map.resources :activity_stream_preferences # map.resources :activity_streams # end # # end ================================================ FILE: lib/activity_streams.rb ================================================ #-- # Copyright (c) 2008 Matson Systems, Inc. # Released under the BSD license found in the file # LICENSE included with this ActivityStreams plug-in. #++ # ActivityStreams # require 'activity_streams/log_activity_streams.rb' require 'controllers/activity_stream_preferences_module.rb' require 'controllers/activity_streams_module.rb' ================================================ FILE: lib/ar_condition.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # class ARCondition attr_reader :conditions def initialize(condition=nil) @conditions = ['1=1'] add(condition) if condition end def add(condition) if condition.is_a?(Array) @conditions.first << " AND (#{condition.first})" @conditions += condition[1..-1] elsif condition.is_a?(String) @conditions.first << " AND (#{condition})" else raise "Unsupported #{condition.class} condition: #{condition}" end self end def <<(condition) add(condition) end end ================================================ FILE: lib/diff.rb ================================================ module RedmineDiff class Diff VERSION = 0.3 def Diff.lcs(a, b) astart = 0 bstart = 0 afinish = a.length-1 bfinish = b.length-1 mvector = [] # First we prune off any common elements at the beginning while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart]) mvector[astart] = bstart astart += 1 bstart += 1 end # now the end while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish]) mvector[afinish] = bfinish afinish -= 1 bfinish -= 1 end bmatches = b.reverse_hash(bstart..bfinish) thresh = [] links = [] (astart..afinish).each { |aindex| aelem = a[aindex] next unless bmatches.has_key? aelem k = nil bmatches[aelem].reverse.each { |bindex| if k && (thresh[k] > bindex) && (thresh[k-1] < bindex) thresh[k] = bindex else k = thresh.replacenextlarger(bindex, k) end links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k } } if !thresh.empty? link = links[thresh.length-1] while link mvector[link[1]] = link[2] link = link[0] end end return mvector end def makediff(a, b) mvector = Diff.lcs(a, b) ai = bi = 0 while ai < mvector.length bline = mvector[ai] if bline while bi < bline discardb(bi, b[bi]) bi += 1 end match(ai, bi) bi += 1 else discarda(ai, a[ai]) end ai += 1 end while ai < a.length discarda(ai, a[ai]) ai += 1 end while bi < b.length discardb(bi, b[bi]) bi += 1 end match(ai, bi) 1 end def compactdiffs diffs = [] @diffs.each { |df| i = 0 curdiff = [] while i < df.length whot = df[i][0] s = @isstring ? df[i][2].chr : [df[i][2]] p = df[i][1] last = df[i][1] i += 1 while df[i] && df[i][0] == whot && df[i][1] == last+1 s << df[i][2] last = df[i][1] i += 1 end curdiff.push [whot, p, s] end diffs.push curdiff } return diffs end attr_reader :diffs, :difftype def initialize(diffs_or_a, b = nil, isstring = nil) if b.nil? @diffs = diffs_or_a @isstring = isstring else @diffs = [] @curdiffs = [] makediff(diffs_or_a, b) @difftype = diffs_or_a.class end end def match(ai, bi) @diffs.push @curdiffs unless @curdiffs.empty? @curdiffs = [] end def discarda(i, elem) @curdiffs.push ['-', i, elem] end def discardb(i, elem) @curdiffs.push ['+', i, elem] end def compact return Diff.new(compactdiffs) end def compact! @diffs = compactdiffs end def inspect @diffs.inspect end end end module Diffable def diff(b) RedmineDiff::Diff.new(self, b) end # Create a hash that maps elements of the array to arrays of indices # where the elements are found. def reverse_hash(range = (0...self.length)) revmap = {} range.each { |i| elem = self[i] if revmap.has_key? elem revmap[elem].push i else revmap[elem] = [i] end } return revmap end def replacenextlarger(value, high = nil) high ||= self.length if self.empty? || value > self[-1] push value return high end # binary search for replacement point low = 0 while low < high index = (high+low)/2 found = self[index] return nil if value == found if value > found low = index + 1 else high = index end end self[low] = value return low end def patch(diff) newary = nil if diff.difftype == String newary = diff.difftype.new('') else newary = diff.difftype.new end ai = 0 bi = 0 diff.diffs.each { |d| d.each { |mod| case mod[0] when '-' while ai < mod[1] newary << self[ai] ai += 1 bi += 1 end ai += 1 when '+' while bi < mod[1] newary << self[ai] ai += 1 bi += 1 end newary << mod[2] bi += 1 else raise "Unknown diff action" end } } while ai < self.length newary << self[ai] ai += 1 bi += 1 end return newary end end class Array include Diffable end class String include Diffable end =begin = Diff (({diff.rb})) - computes the differences between two arrays or strings. Copyright (C) 2001 Lars Christensen == Synopsis diff = Diff.new(a, b) b = a.patch(diff) == Class Diff === Class Methods --- Diff.new(a, b) --- a.diff(b) Creates a Diff object which represent the differences between ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays of any objects, strings, or object of any class that include module ((|Diffable|)) == Module Diffable The module ((|Diffable|)) is intended to be included in any class for which differences are to be computed. Diffable is included into String and Array when (({diff.rb})) is (({require}))'d. Classes including Diffable should implement (({[]})) to get element at integer indices, (({<<})) to append elements to the object and (({ClassName#new})) should accept 0 arguments to create a new empty object. === Instance Methods --- Diffable#patch(diff) Applies the differences from ((|diff|)) to the object ((|obj|)) and return the result. ((|obj|)) is not changed. ((|obj|)) and can be either an array or a string, but must match the object from which the ((|diff|)) was created. =end ================================================ FILE: lib/faster_csv.rb ================================================ #!/usr/local/bin/ruby -w # = faster_csv.rb -- Faster CSV Reading and Writing # # Created by James Edward Gray II on 2005-10-31. # Copyright 2005 Gray Productions. All rights reserved. # # See FasterCSV for documentation. if RUBY_VERSION >= "1.9" abort <<-VERSION_WARNING.gsub(/^\s+/, "") Please switch to Ruby 1.9's standard CSV library. It's FasterCSV plus support for Ruby 1.9's m17n encoding engine. VERSION_WARNING end require "forwardable" require "English" require "enumerator" require "date" require "stringio" # # This class provides a complete interface to CSV files and data. It offers # tools to enable you to read and write to and from Strings or IO objects, as # needed. # # == Reading # # === From a File # # ==== A Line at a Time # # FasterCSV.foreach("path/to/file.csv") do |row| # # use row here... # end # # ==== All at Once # # arr_of_arrs = FasterCSV.read("path/to/file.csv") # # === From a String # # ==== A Line at a Time # # FasterCSV.parse("CSV,data,String") do |row| # # use row here... # end # # ==== All at Once # # arr_of_arrs = FasterCSV.parse("CSV,data,String") # # == Writing # # === To a File # # FasterCSV.open("path/to/file.csv", "w") do |csv| # csv << ["row", "of", "CSV", "data"] # csv << ["another", "row"] # # ... # end # # === To a String # # csv_string = FasterCSV.generate do |csv| # csv << ["row", "of", "CSV", "data"] # csv << ["another", "row"] # # ... # end # # == Convert a Single Line # # csv_string = ["CSV", "data"].to_csv # to CSV # csv_array = "CSV,String".parse_csv # from CSV # # == Shortcut Interface # # FCSV { |csv_out| csv_out << %w{my data here} } # to $stdout # FCSV(csv = "") { |csv_str| csv_str << %w{my data here} } # to a String # FCSV($stderr) { |csv_err| csv_err << %w{my data here} } # to $stderr # class FasterCSV # The version of the installed library. VERSION = "1.5.0".freeze # # A FasterCSV::Row is part Array and part Hash. It retains an order for the # fields and allows duplicates just as an Array would, but also allows you to # access fields by name just as you could if they were in a Hash. # # All rows returned by FasterCSV will be constructed from this class, if # header row processing is activated. # class Row # # Construct a new FasterCSV::Row from +headers+ and +fields+, which are # expected to be Arrays. If one Array is shorter than the other, it will be # padded with +nil+ objects. # # The optional +header_row+ parameter can be set to +true+ to indicate, via # FasterCSV::Row.header_row?() and FasterCSV::Row.field_row?(), that this is # a header row. Otherwise, the row is assumes to be a field row. # # A FasterCSV::Row object supports the following Array methods through # delegation: # # * empty?() # * length() # * size() # def initialize(headers, fields, header_row = false) @header_row = header_row # handle extra headers or fields @row = if headers.size > fields.size headers.zip(fields) else fields.zip(headers).map { |pair| pair.reverse } end end # Internal data format used to compare equality. attr_reader :row protected :row ### Array Delegation ### extend Forwardable def_delegators :@row, :empty?, :length, :size # Returns +true+ if this is a header row. def header_row? @header_row end # Returns +true+ if this is a field row. def field_row? not header_row? end # Returns the headers of this row. def headers @row.map { |pair| pair.first } end # # :call-seq: # field( header ) # field( header, offset ) # field( index ) # # This method will fetch the field value by +header+ or +index+. If a field # is not found, +nil+ is returned. # # When provided, +offset+ ensures that a header match occurrs on or later # than the +offset+ index. You can use this to find duplicate headers, # without resorting to hard-coding exact indices. # def field(header_or_index, minimum_index = 0) # locate the pair finder = header_or_index.is_a?(Integer) ? :[] : :assoc pair = @row[minimum_index..-1].send(finder, header_or_index) # return the field if we have a pair pair.nil? ? nil : pair.last end alias_method :[], :field # # :call-seq: # []=( header, value ) # []=( header, offset, value ) # []=( index, value ) # # Looks up the field by the semantics described in FasterCSV::Row.field() # and assigns the +value+. # # Assigning past the end of the row with an index will set all pairs between # to [nil, nil]. Assigning to an unused header appends the new # pair. # def []=(*args) value = args.pop if args.first.is_a? Integer if @row[args.first].nil? # extending past the end with index @row[args.first] = [nil, value] @row.map! { |pair| pair.nil? ? [nil, nil] : pair } else # normal index assignment @row[args.first][1] = value end else index = index(*args) if index.nil? # appending a field self << [args.first, value] else # normal header assignment @row[index][1] = value end end end # # :call-seq: # <<( field ) # <<( header_and_field_array ) # <<( header_and_field_hash ) # # If a two-element Array is provided, it is assumed to be a header and field # and the pair is appended. A Hash works the same way with the key being # the header and the value being the field. Anything else is assumed to be # a lone field which is appended with a +nil+ header. # # This method returns the row for chaining. # def <<(arg) if arg.is_a?(Array) and arg.size == 2 # appending a header and name @row << arg elsif arg.is_a?(Hash) # append header and name pairs arg.each { |pair| @row << pair } else # append field value @row << [nil, arg] end self # for chaining end # # A shortcut for appending multiple fields. Equivalent to: # # args.each { |arg| faster_csv_row << arg } # # This method returns the row for chaining. # def push(*args) args.each { |arg| self << arg } self # for chaining end # # :call-seq: # delete( header ) # delete( header, offset ) # delete( index ) # # Used to remove a pair from the row by +header+ or +index+. The pair is # located as described in FasterCSV::Row.field(). The deleted pair is # returned, or +nil+ if a pair could not be found. # def delete(header_or_index, minimum_index = 0) if header_or_index.is_a? Integer # by index @row.delete_at(header_or_index) else # by header @row.delete_at(index(header_or_index, minimum_index)) end end # # The provided +block+ is passed a header and field for each pair in the row # and expected to return +true+ or +false+, depending on whether the pair # should be deleted. # # This method returns the row for chaining. # def delete_if(&block) @row.delete_if(&block) self # for chaining end # # This method accepts any number of arguments which can be headers, indices, # Ranges of either, or two-element Arrays containing a header and offset. # Each argument will be replaced with a field lookup as described in # FasterCSV::Row.field(). # # If called with no arguments, all fields are returned. # def fields(*headers_and_or_indices) if headers_and_or_indices.empty? # return all fields--no arguments @row.map { |pair| pair.last } else # or work like values_at() headers_and_or_indices.inject(Array.new) do |all, h_or_i| all + if h_or_i.is_a? Range index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin : index(h_or_i.begin) index_end = h_or_i.end.is_a?(Integer) ? h_or_i.end : index(h_or_i.end) new_range = h_or_i.exclude_end? ? (index_begin...index_end) : (index_begin..index_end) fields.values_at(new_range) else [field(*Array(h_or_i))] end end end end alias_method :values_at, :fields # # :call-seq: # index( header ) # index( header, offset ) # # This method will return the index of a field with the provided +header+. # The +offset+ can be used to locate duplicate header names, as described in # FasterCSV::Row.field(). # def index(header, minimum_index = 0) # find the pair index = headers[minimum_index..-1].index(header) # return the index at the right offset, if we found one index.nil? ? nil : index + minimum_index end # Returns +true+ if +name+ is a header for this row, and +false+ otherwise. def header?(name) headers.include? name end alias_method :include?, :header? # # Returns +true+ if +data+ matches a field in this row, and +false+ # otherwise. # def field?(data) fields.include? data end include Enumerable # # Yields each pair of the row as header and field tuples (much like # iterating over a Hash). # # Support for Enumerable. # # This method returns the row for chaining. # def each(&block) @row.each(&block) self # for chaining end # # Returns +true+ if this row contains the same headers and fields in the # same order as +other+. # def ==(other) @row == other.row end # # Collapses the row into a simple Hash. Be warning that this discards field # order and clobbers duplicate fields. # def to_hash # flatten just one level of the internal Array Hash[*@row.inject(Array.new) { |ary, pair| ary.push(*pair) }] end # # Returns the row as a CSV String. Headers are not used. Equivalent to: # # faster_csv_row.fields.to_csv( options ) # def to_csv(options = Hash.new) fields.to_csv(options) end alias_method :to_s, :to_csv # A summary of fields, by header. def inspect str = "#<#{self.class}" each do |header, field| str << " #{header.is_a?(Symbol) ? header.to_s : header.inspect}:" << field.inspect end str << ">" end end # # A FasterCSV::Table is a two-dimensional data structure for representing CSV # documents. Tables allow you to work with the data by row or column, # manipulate the data, and even convert the results back to CSV, if needed. # # All tables returned by FasterCSV will be constructed from this class, if # header row processing is activated. # class Table # # Construct a new FasterCSV::Table from +array_of_rows+, which are expected # to be FasterCSV::Row objects. All rows are assumed to have the same # headers. # # A FasterCSV::Table object supports the following Array methods through # delegation: # # * empty?() # * length() # * size() # def initialize(array_of_rows) @table = array_of_rows @mode = :col_or_row end # The current access mode for indexing and iteration. attr_reader :mode # Internal data format used to compare equality. attr_reader :table protected :table ### Array Delegation ### extend Forwardable def_delegators :@table, :empty?, :length, :size # # Returns a duplicate table object, in column mode. This is handy for # chaining in a single call without changing the table mode, but be aware # that this method can consume a fair amount of memory for bigger data sets. # # This method returns the duplicate table for chaining. Don't chain # destructive methods (like []=()) this way though, since you are working # with a duplicate. # def by_col self.class.new(@table.dup).by_col! end # # Switches the mode of this table to column mode. All calls to indexing and # iteration methods will work with columns until the mode is changed again. # # This method returns the table and is safe to chain. # def by_col! @mode = :col self end # # Returns a duplicate table object, in mixed mode. This is handy for # chaining in a single call without changing the table mode, but be aware # that this method can consume a fair amount of memory for bigger data sets. # # This method returns the duplicate table for chaining. Don't chain # destructive methods (like []=()) this way though, since you are working # with a duplicate. # def by_col_or_row self.class.new(@table.dup).by_col_or_row! end # # Switches the mode of this table to mixed mode. All calls to indexing and # iteration methods will use the default intelligent indexing system until # the mode is changed again. In mixed mode an index is assumed to be a row # reference while anything else is assumed to be column access by headers. # # This method returns the table and is safe to chain. # def by_col_or_row! @mode = :col_or_row self end # # Returns a duplicate table object, in row mode. This is handy for chaining # in a single call without changing the table mode, but be aware that this # method can consume a fair amount of memory for bigger data sets. # # This method returns the duplicate table for chaining. Don't chain # destructive methods (like []=()) this way though, since you are working # with a duplicate. # def by_row self.class.new(@table.dup).by_row! end # # Switches the mode of this table to row mode. All calls to indexing and # iteration methods will work with rows until the mode is changed again. # # This method returns the table and is safe to chain. # def by_row! @mode = :row self end # # Returns the headers for the first row of this table (assumed to match all # other rows). An empty Array is returned for empty tables. # def headers if @table.empty? Array.new else @table.first.headers end end # # In the default mixed mode, this method returns rows for index access and # columns for header access. You can force the index association by first # calling by_col!() or by_row!(). # # Columns are returned as an Array of values. Altering that Array has no # effect on the table. # def [](index_or_header) if @mode == :row or # by index (@mode == :col_or_row and index_or_header.is_a? Integer) @table[index_or_header] else # by header @table.map { |row| row[index_or_header] } end end # # In the default mixed mode, this method assigns rows for index access and # columns for header access. You can force the index association by first # calling by_col!() or by_row!(). # # Rows may be set to an Array of values (which will inherit the table's # headers()) or a FasterCSV::Row. # # Columns may be set to a single value, which is copied to each row of the # column, or an Array of values. Arrays of values are assigned to rows top # to bottom in row major order. Excess values are ignored and if the Array # does not have a value for each row the extra rows will receive a +nil+. # # Assigning to an existing column or row clobbers the data. Assigning to # new columns creates them at the right end of the table. # def []=(index_or_header, value) if @mode == :row or # by index (@mode == :col_or_row and index_or_header.is_a? Integer) if value.is_a? Array @table[index_or_header] = Row.new(headers, value) else @table[index_or_header] = value end else # set column if value.is_a? Array # multiple values @table.each_with_index do |row, i| if row.header_row? row[index_or_header] = index_or_header else row[index_or_header] = value[i] end end else # repeated value @table.each do |row| if row.header_row? row[index_or_header] = index_or_header else row[index_or_header] = value end end end end end # # The mixed mode default is to treat a list of indices as row access, # returning the rows indicated. Anything else is considered columnar # access. For columnar access, the return set has an Array for each row # with the values indicated by the headers in each Array. You can force # column or row mode using by_col!() or by_row!(). # # You cannot mix column and row access. # def values_at(*indices_or_headers) if @mode == :row or # by indices ( @mode == :col_or_row and indices_or_headers.all? do |index| index.is_a?(Integer) or ( index.is_a?(Range) and index.first.is_a?(Integer) and index.last.is_a?(Integer) ) end ) @table.values_at(*indices_or_headers) else # by headers @table.map { |row| row.values_at(*indices_or_headers) } end end # # Adds a new row to the bottom end of this table. You can provide an Array, # which will be converted to a FasterCSV::Row (inheriting the table's # headers()), or a FasterCSV::Row. # # This method returns the table for chaining. # def <<(row_or_array) if row_or_array.is_a? Array # append Array @table << Row.new(headers, row_or_array) else # append Row @table << row_or_array end self # for chaining end # # A shortcut for appending multiple rows. Equivalent to: # # rows.each { |row| self << row } # # This method returns the table for chaining. # def push(*rows) rows.each { |row| self << row } self # for chaining end # # Removes and returns the indicated column or row. In the default mixed # mode indices refer to rows and everything else is assumed to be a column # header. Use by_col!() or by_row!() to force the lookup. # def delete(index_or_header) if @mode == :row or # by index (@mode == :col_or_row and index_or_header.is_a? Integer) @table.delete_at(index_or_header) else # by header @table.map { |row| row.delete(index_or_header).last } end end # # Removes any column or row for which the block returns +true+. In the # default mixed mode or row mode, iteration is the standard row major # walking of rows. In column mode, interation will +yield+ two element # tuples containing the column name and an Array of values for that column. # # This method returns the table for chaining. # def delete_if(&block) if @mode == :row or @mode == :col_or_row # by index @table.delete_if(&block) else # by header to_delete = Array.new headers.each_with_index do |header, i| to_delete << header if block[[header, self[header]]] end to_delete.map { |header| delete(header) } end self # for chaining end include Enumerable # # In the default mixed mode or row mode, iteration is the standard row major # walking of rows. In column mode, interation will +yield+ two element # tuples containing the column name and an Array of values for that column. # # This method returns the table for chaining. # def each(&block) if @mode == :col headers.each { |header| block[[header, self[header]]] } else @table.each(&block) end self # for chaining end # Returns +true+ if all rows of this table ==() +other+'s rows. def ==(other) @table == other.table end # # Returns the table as an Array of Arrays. Headers will be the first row, # then all of the field rows will follow. # def to_a @table.inject([headers]) do |array, row| if row.header_row? array else array + [row.fields] end end end # # Returns the table as a complete CSV String. Headers will be listed first, # then all of the field rows. # def to_csv(options = Hash.new) @table.inject([headers.to_csv(options)]) do |rows, row| if row.header_row? rows else rows + [row.fields.to_csv(options)] end end.join end alias_method :to_s, :to_csv def inspect "#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>" end end # The error thrown when the parser encounters illegal CSV formatting. class MalformedCSVError < RuntimeError; end # # A FieldInfo Struct contains details about a field's position in the data # source it was read from. FasterCSV will pass this Struct to some blocks # that make decisions based on field structure. See # FasterCSV.convert_fields() for an example. # # index:: The zero-based index of the field in its row. # line:: The line of the data source this row is from. # header:: The header for the column, when available. # FieldInfo = Struct.new(:index, :line, :header) # A Regexp used to find and convert some common Date formats. DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} | \d{4}-\d{2}-\d{2} )\z /x # A Regexp used to find and convert some common DateTime formats. DateTimeMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} | \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} )\z /x # # This Hash holds the built-in converters of FasterCSV that can be accessed by # name. You can select Converters with FasterCSV.convert() or through the # +options+ Hash passed to FasterCSV::new(). # # :integer:: Converts any field Integer() accepts. # :float:: Converts any field Float() accepts. # :numeric:: A combination of :integer # and :float. # :date:: Converts any field Date::parse() accepts. # :date_time:: Converts any field DateTime::parse() accepts. # :all:: All built-in converters. A combination of # :date_time and :numeric. # # This Hash is intetionally left unfrozen and users should feel free to add # values to it that can be accessed by all FasterCSV objects. # # To add a combo field, the value should be an Array of names. Combo fields # can be nested with other combo fields. # Converters = { :integer => lambda { |f| Integer(f) rescue f }, :float => lambda { |f| Float(f) rescue f }, :numeric => [:integer, :float], :date => lambda { |f| f =~ DateMatcher ? (Date.parse(f) rescue f) : f }, :date_time => lambda { |f| f =~ DateTimeMatcher ? (DateTime.parse(f) rescue f) : f }, :all => [:date_time, :numeric] } # # This Hash holds the built-in header converters of FasterCSV that can be # accessed by name. You can select HeaderConverters with # FasterCSV.header_convert() or through the +options+ Hash passed to # FasterCSV::new(). # # :downcase:: Calls downcase() on the header String. # :symbol:: The header String is downcased, spaces are # replaced with underscores, non-word characters # are dropped, and finally to_sym() is called. # # This Hash is intetionally left unfrozen and users should feel free to add # values to it that can be accessed by all FasterCSV objects. # # To add a combo field, the value should be an Array of names. Combo fields # can be nested with other combo fields. # HeaderConverters = { :downcase => lambda { |h| h.downcase }, :symbol => lambda { |h| h.downcase.tr(" ", "_").delete("^a-z0-9_").to_sym } } # # The options used when no overrides are given by calling code. They are: # # :col_sep:: "," # :row_sep:: :auto # :quote_char:: '"' # :converters:: +nil+ # :unconverted_fields:: +nil+ # :headers:: +false+ # :return_headers:: +false+ # :header_converters:: +nil+ # :skip_blanks:: +false+ # :force_quotes:: +false+ # DEFAULT_OPTIONS = { :col_sep => ",", :row_sep => :auto, :quote_char => '"', :converters => nil, :unconverted_fields => nil, :headers => false, :return_headers => false, :header_converters => nil, :skip_blanks => false, :force_quotes => false }.freeze # # This method will build a drop-in replacement for many of the standard CSV # methods. It allows you to write code like: # # begin # require "faster_csv" # FasterCSV.build_csv_interface # rescue LoadError # require "csv" # end # # ... use CSV here ... # # This is not a complete interface with completely identical behavior. # However, it is intended to be close enough that you won't notice the # difference in most cases. CSV methods supported are: # # * foreach() # * generate_line() # * open() # * parse() # * parse_line() # * readlines() # # Be warned that this interface is slower than vanilla FasterCSV due to the # extra layer of method calls. Depending on usage, this can slow it down to # near CSV speeds. # def self.build_csv_interface Object.const_set(:CSV, Class.new).class_eval do def self.foreach(path, rs = :auto, &block) # :nodoc: FasterCSV.foreach(path, :row_sep => rs, &block) end def self.generate_line(row, fs = ",", rs = "") # :nodoc: FasterCSV.generate_line(row, :col_sep => fs, :row_sep => rs) end def self.open(path, mode, fs = ",", rs = :auto, &block) # :nodoc: if block and mode.include? "r" FasterCSV.open(path, mode, :col_sep => fs, :row_sep => rs) do |csv| csv.each(&block) end else FasterCSV.open(path, mode, :col_sep => fs, :row_sep => rs, &block) end end def self.parse(str_or_readable, fs = ",", rs = :auto, &block) # :nodoc: FasterCSV.parse(str_or_readable, :col_sep => fs, :row_sep => rs, &block) end def self.parse_line(src, fs = ",", rs = :auto) # :nodoc: FasterCSV.parse_line(src, :col_sep => fs, :row_sep => rs) end def self.readlines(path, rs = :auto) # :nodoc: FasterCSV.readlines(path, :row_sep => rs) end end end # # This method allows you to serialize an Array of Ruby objects to a String or # File of CSV data. This is not as powerful as Marshal or YAML, but perhaps # useful for spreadsheet and database interaction. # # Out of the box, this method is intended to work with simple data objects or # Structs. It will serialize a list of instance variables and/or # Struct.members(). # # If you need need more complicated serialization, you can control the process # by adding methods to the class to be serialized. # # A class method csv_meta() is responsible for returning the first row of the # document (as an Array). This row is considered to be a Hash of the form # key_1,value_1,key_2,value_2,... FasterCSV::load() expects to find a class # key with a value of the stringified class name and FasterCSV::dump() will # create this, if you do not define this method. This method is only called # on the first object of the Array. # # The next method you can provide is an instance method called csv_headers(). # This method is expected to return the second line of the document (again as # an Array), which is to be used to give each column a header. By default, # FasterCSV::load() will set an instance variable if the field header starts # with an @ character or call send() passing the header as the method name and # the field value as an argument. This method is only called on the first # object of the Array. # # Finally, you can provide an instance method called csv_dump(), which will # be passed the headers. This should return an Array of fields that can be # serialized for this object. This method is called once for every object in # the Array. # # The +io+ parameter can be used to serialize to a File, and +options+ can be # anything FasterCSV::new() accepts. # def self.dump(ary_of_objs, io = "", options = Hash.new) obj_template = ary_of_objs.first csv = FasterCSV.new(io, options) # write meta information begin csv << obj_template.class.csv_meta rescue NoMethodError csv << [:class, obj_template.class] end # write headers begin headers = obj_template.csv_headers rescue NoMethodError headers = obj_template.instance_variables.sort if obj_template.class.ancestors.find { |cls| cls.to_s =~ /\AStruct\b/ } headers += obj_template.members.map { |mem| "#{mem}=" }.sort end end csv << headers # serialize each object ary_of_objs.each do |obj| begin csv << obj.csv_dump(headers) rescue NoMethodError csv << headers.map do |var| if var[0] == ?@ obj.instance_variable_get(var) else obj[var[0..-2]] end end end end if io.is_a? String csv.string else csv.close end end # # :call-seq: # filter( options = Hash.new ) { |row| ... } # filter( input, options = Hash.new ) { |row| ... } # filter( input, output, options = Hash.new ) { |row| ... } # # This method is a convenience for building Unix-like filters for CSV data. # Each row is yielded to the provided block which can alter it as needed. # After the block returns, the row is appended to +output+ altered or not. # # The +input+ and +output+ arguments can be anything FasterCSV::new() accepts # (generally String or IO objects). If not given, they default to # ARGF and $stdout. # # The +options+ parameter is also filtered down to FasterCSV::new() after some # clever key parsing. Any key beginning with :in_ or # :input_ will have that leading identifier stripped and will only # be used in the +options+ Hash for the +input+ object. Keys starting with # :out_ or :output_ affect only +output+. All other keys # are assigned to both objects. # # The :output_row_sep +option+ defaults to # $INPUT_RECORD_SEPARATOR ($/). # def self.filter(*args) # parse options for input, output, or both in_options, out_options = Hash.new, {:row_sep => $INPUT_RECORD_SEPARATOR} if args.last.is_a? Hash args.pop.each do |key, value| case key.to_s when /\Ain(?:put)?_(.+)\Z/ in_options[$1.to_sym] = value when /\Aout(?:put)?_(.+)\Z/ out_options[$1.to_sym] = value else in_options[key] = value out_options[key] = value end end end # build input and output wrappers input = FasterCSV.new(args.shift || ARGF, in_options) output = FasterCSV.new(args.shift || $stdout, out_options) # read, yield, write input.each do |row| yield row output << row end end # # This method is intended as the primary interface for reading CSV files. You # pass a +path+ and any +options+ you wish to set for the read. Each row of # file will be passed to the provided +block+ in turn. # # The +options+ parameter can be anything FasterCSV::new() understands. # def self.foreach(path, options = Hash.new, &block) open(path, "rb", options) do |csv| csv.each(&block) end end # # :call-seq: # generate( str, options = Hash.new ) { |faster_csv| ... } # generate( options = Hash.new ) { |faster_csv| ... } # # This method wraps a String you provide, or an empty default String, in a # FasterCSV object which is passed to the provided block. You can use the # block to append CSV rows to the String and when the block exits, the # final String will be returned. # # Note that a passed String *is* modfied by this method. Call dup() before # passing if you need a new String. # # The +options+ parameter can be anthing FasterCSV::new() understands. # def self.generate(*args) # add a default empty String, if none was given if args.first.is_a? String io = StringIO.new(args.shift) io.seek(0, IO::SEEK_END) args.unshift(io) else args.unshift("") end faster_csv = new(*args) # wrap yield faster_csv # yield for appending faster_csv.string # return final String end # # This method is a shortcut for converting a single row (Array) into a CSV # String. # # The +options+ parameter can be anthing FasterCSV::new() understands. # # The :row_sep +option+ defaults to $INPUT_RECORD_SEPARATOR # ($/) when calling this method. # def self.generate_line(row, options = Hash.new) options = {:row_sep => $INPUT_RECORD_SEPARATOR}.merge(options) (new("", options) << row).string end # # This method will return a FasterCSV instance, just like FasterCSV::new(), # but the instance will be cached and returned for all future calls to this # method for the same +data+ object (tested by Object#object_id()) with the # same +options+. # # If a block is given, the instance is passed to the block and the return # value becomes the return value of the block. # def self.instance(data = $stdout, options = Hash.new) # create a _signature_ for this method call, data object and options sig = [data.object_id] + options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s }) # fetch or create the instance for this signature @@instances ||= Hash.new instance = (@@instances[sig] ||= new(data, options)) if block_given? yield instance # run block, if given, returning result else instance # or return the instance end end # # This method is the reading counterpart to FasterCSV::dump(). See that # method for a detailed description of the process. # # You can customize loading by adding a class method called csv_load() which # will be passed a Hash of meta information, an Array of headers, and an Array # of fields for the object the method is expected to return. # # Remember that all fields will be Strings after this load. If you need # something else, use +options+ to setup converters or provide a custom # csv_load() implementation. # def self.load(io_or_str, options = Hash.new) csv = FasterCSV.new(io_or_str, options) # load meta information meta = Hash[*csv.shift] cls = meta["class"].split("::").inject(Object) do |c, const| c.const_get(const) end # load headers headers = csv.shift # unserialize each object stored in the file results = csv.inject(Array.new) do |all, row| begin obj = cls.csv_load(meta, headers, row) rescue NoMethodError obj = cls.allocate headers.zip(row) do |name, value| if name[0] == ?@ obj.instance_variable_set(name, value) else obj.send(name, value) end end end all << obj end csv.close unless io_or_str.is_a? String results end # # :call-seq: # open( filename, mode="rb", options = Hash.new ) { |faster_csv| ... } # open( filename, mode="rb", options = Hash.new ) # # This method opens an IO object, and wraps that with FasterCSV. This is # intended as the primary interface for writing a CSV file. # # You may pass any +args+ Ruby's open() understands followed by an optional # Hash containing any +options+ FasterCSV::new() understands. # # This method works like Ruby's open() call, in that it will pass a FasterCSV # object to a provided block and close it when the block termminates, or it # will return the FasterCSV object when no block is provided. (*Note*: This # is different from the standard CSV library which passes rows to the block. # Use FasterCSV::foreach() for that behavior.) # # An opened FasterCSV object will delegate to many IO methods, for # convenience. You may call: # # * binmode() # * close() # * close_read() # * close_write() # * closed?() # * eof() # * eof?() # * fcntl() # * fileno() # * flush() # * fsync() # * ioctl() # * isatty() # * pid() # * pos() # * reopen() # * seek() # * stat() # * sync() # * sync=() # * tell() # * to_i() # * to_io() # * tty?() # def self.open(*args) # find the +options+ Hash options = if args.last.is_a? Hash then args.pop else Hash.new end # default to a binary open mode args << "rb" if args.size == 1 # wrap a File opened with the remaining +args+ csv = new(File.open(*args), options) # handle blocks like Ruby's open(), not like the CSV library if block_given? begin yield csv ensure csv.close end else csv end end # # :call-seq: # parse( str, options = Hash.new ) { |row| ... } # parse( str, options = Hash.new ) # # This method can be used to easily parse CSV out of a String. You may either # provide a +block+ which will be called with each row of the String in turn, # or just use the returned Array of Arrays (when no +block+ is given). # # You pass your +str+ to read from, and an optional +options+ Hash containing # anything FasterCSV::new() understands. # def self.parse(*args, &block) csv = new(*args) if block.nil? # slurp contents, if no block is given begin csv.read ensure csv.close end else # or pass each row to a provided block csv.each(&block) end end # # This method is a shortcut for converting a single line of a CSV String into # a into an Array. Note that if +line+ contains multiple rows, anything # beyond the first row is ignored. # # The +options+ parameter can be anthing FasterCSV::new() understands. # def self.parse_line(line, options = Hash.new) new(line, options).shift end # # Use to slurp a CSV file into an Array of Arrays. Pass the +path+ to the # file and any +options+ FasterCSV::new() understands. # def self.read(path, options = Hash.new) open(path, "rb", options) { |csv| csv.read } end # Alias for FasterCSV::read(). def self.readlines(*args) read(*args) end # # A shortcut for: # # FasterCSV.read( path, { :headers => true, # :converters => :numeric, # :header_converters => :symbol }.merge(options) ) # def self.table(path, options = Hash.new) read( path, { :headers => true, :converters => :numeric, :header_converters => :symbol }.merge(options) ) end # # This constructor will wrap either a String or IO object passed in +data+ for # reading and/or writing. In addition to the FasterCSV instance methods, # several IO methods are delegated. (See FasterCSV::open() for a complete # list.) If you pass a String for +data+, you can later retrieve it (after # writing to it, for example) with FasterCSV.string(). # # Note that a wrapped String will be positioned at at the beginning (for # reading). If you want it at the end (for writing), use # FasterCSV::generate(). If you want any other positioning, pass a preset # StringIO object instead. # # You may set any reading and/or writing preferences in the +options+ Hash. # Available options are: # # :col_sep:: The String placed between each field. # :row_sep:: The String appended to the end of each # row. This can be set to the special # :auto setting, which requests # that FasterCSV automatically discover # this from the data. Auto-discovery # reads ahead in the data looking for # the next "\r\n", # "\n", or "\r" # sequence. A sequence will be selected # even if it occurs in a quoted field, # assuming that you would have the same # line endings there. If none of those # sequences is found, +data+ is # ARGF, STDIN, # STDOUT, or STDERR, # or the stream is only available for # output, the default # $INPUT_RECORD_SEPARATOR # ($/) is used. Obviously, # discovery takes a little time. Set # manually if speed is important. Also # note that IO objects should be opened # in binary mode on Windows if this # feature will be used as the # line-ending translation can cause # problems with resetting the document # position to where it was before the # read ahead. # :quote_char:: The character used to quote fields. # This has to be a single character # String. This is useful for # application that incorrectly use # ' as the quote character # instead of the correct ". # FasterCSV will always consider a # double sequence this character to be # an escaped quote. # :encoding:: The encoding to use when parsing the # file. Defaults to your $KDOCE # setting. Valid values: `n’ or # `N’ for none, `e’ or # `E’ for EUC, `s’ or # `S’ for SJIS, and # `u’ or `U’ for UTF-8 # (see Regexp.new()). # :field_size_limit:: This is a maximum size FasterCSV will # read ahead looking for the closing # quote for a field. (In truth, it # reads to the first line ending beyond # this size.) If a quote cannot be # found within the limit FasterCSV will # raise a MalformedCSVError, assuming # the data is faulty. You can use this # limit to prevent what are effectively # DoS attacks on the parser. However, # this limit can cause a legitimate # parse to fail and thus is set to # +nil+, or off, by default. # :converters:: An Array of names from the Converters # Hash and/or lambdas that handle custom # conversion. A single converter # doesn't have to be in an Array. # :unconverted_fields:: If set to +true+, an # unconverted_fields() method will be # added to all returned rows (Array or # FasterCSV::Row) that will return the # fields as they were before convertion. # Note that :headers supplied # by Array or String were not fields of # the document and thus will have an # empty Array attached. # :headers:: If set to :first_row or # +true+, the initial row of the CSV # file will be treated as a row of # headers. If set to an Array, the # contents will be used as the headers. # If set to a String, the String is run # through a call of # FasterCSV::parse_line() with the same # :col_sep, :row_sep, # and :quote_char as this # instance to produce an Array of # headers. This setting causes # FasterCSV.shift() to return rows as # FasterCSV::Row objects instead of # Arrays and FasterCSV.read() to return # FasterCSV::Table objects instead of # an Array of Arrays. # :return_headers:: When +false+, header rows are silently # swallowed. If set to +true+, header # rows are returned in a FasterCSV::Row # object with identical headers and # fields (save that the fields do not go # through the converters). # :write_headers:: When +true+ and :headers is # set, a header row will be added to the # output. # :header_converters:: Identical in functionality to # :converters save that the # conversions are only made to header # rows. # :skip_blanks:: When set to a +true+ value, FasterCSV # will skip over any rows with no # content. # :force_quotes:: When set to a +true+ value, FasterCSV # will quote all CSV fields it creates. # # See FasterCSV::DEFAULT_OPTIONS for the default settings. # # Options cannot be overriden in the instance methods for performance reasons, # so be sure to set what you want here. # def initialize(data, options = Hash.new) # build the options for this read/write options = DEFAULT_OPTIONS.merge(options) # create the IO object we will read from @io = if data.is_a? String then StringIO.new(data) else data end init_separators(options) init_parsers(options) init_converters(options) init_headers(options) unless options.empty? raise ArgumentError, "Unknown options: #{options.keys.join(', ')}." end # track our own lineno since IO gets confused about line-ends is CSV fields @lineno = 0 end # # The line number of the last row read from this file. Fields with nested # line-end characters will not affect this count. # attr_reader :lineno ### IO and StringIO Delegation ### extend Forwardable def_delegators :@io, :binmode, :close, :close_read, :close_write, :closed?, :eof, :eof?, :fcntl, :fileno, :flush, :fsync, :ioctl, :isatty, :pid, :pos, :reopen, :seek, :stat, :string, :sync, :sync=, :tell, :to_i, :to_io, :tty? # Rewinds the underlying IO object and resets FasterCSV's lineno() counter. def rewind @headers = nil @lineno = 0 @io.rewind end ### End Delegation ### # # The primary write method for wrapped Strings and IOs, +row+ (an Array or # FasterCSV::Row) is converted to CSV and appended to the data source. When a # FasterCSV::Row is passed, only the row's fields() are appended to the # output. # # The data source must be open for writing. # def <<(row) # make sure headers have been assigned if header_row? and [Array, String].include? @use_headers.class parse_headers # won't read data for Array or String self << @headers if @write_headers end # Handle FasterCSV::Row objects and Hashes row = case row when self.class::Row then row.fields when Hash then @headers.map { |header| row[header] } else row end @headers = row if header_row? @lineno += 1 @io << row.map(&@quote).join(@col_sep) + @row_sep # quote and separate self # for chaining end alias_method :add_row, :<< alias_method :puts, :<< # # :call-seq: # convert( name ) # convert { |field| ... } # convert { |field, field_info| ... } # # You can use this method to install a FasterCSV::Converters built-in, or # provide a block that handles a custom conversion. # # If you provide a block that takes one argument, it will be passed the field # and is expected to return the converted value or the field itself. If your # block takes two arguments, it will also be passed a FieldInfo Struct, # containing details about the field. Again, the block should return a # converted field or the field itself. # def convert(name = nil, &converter) add_converter(:converters, self.class::Converters, name, &converter) end # # :call-seq: # header_convert( name ) # header_convert { |field| ... } # header_convert { |field, field_info| ... } # # Identical to FasterCSV.convert(), but for header rows. # # Note that this method must be called before header rows are read to have any # effect. # def header_convert(name = nil, &converter) add_converter( :header_converters, self.class::HeaderConverters, name, &converter ) end include Enumerable # # Yields each row of the data source in turn. # # Support for Enumerable. # # The data source must be open for reading. # def each while row = shift yield row end end # # Slurps the remaining rows and returns an Array of Arrays. # # The data source must be open for reading. # def read rows = to_a if @use_headers Table.new(rows) else rows end end alias_method :readlines, :read # Returns +true+ if the next row read will be a header row. def header_row? @use_headers and @headers.nil? end # # The primary read method for wrapped Strings and IOs, a single row is pulled # from the data source, parsed and returned as an Array of fields (if header # rows are not used) or a FasterCSV::Row (when header rows are used). # # The data source must be open for reading. # def shift ######################################################################### ### This method is purposefully kept a bit long as simple conditional ### ### checks are faster than numerous (expensive) method calls. ### ######################################################################### # handle headers not based on document content if header_row? and @return_headers and [Array, String].include? @use_headers.class if @unconverted_fields return add_unconverted_fields(parse_headers, Array.new) else return parse_headers end end # begin with a blank line, so we can always add to it line = String.new # # it can take multiple calls to @io.gets() to get a full line, # because of \r and/or \n characters embedded in quoted fields # loop do # add another read to the line begin line += @io.gets(@row_sep) rescue return nil end # copy the line so we can chop it up in parsing parse = line.dup parse.sub!(@parsers[:line_end], "") # # I believe a blank line should be an Array.new, not # CSV's [nil] # if parse.empty? @lineno += 1 if @skip_blanks line = "" next elsif @unconverted_fields return add_unconverted_fields(Array.new, Array.new) elsif @use_headers return FasterCSV::Row.new(Array.new, Array.new) else return Array.new end end # parse the fields with a mix of String#split and regular expressions csv = Array.new current_field = String.new field_quotes = 0 parse.split(@col_sep, -1).each do |match| if current_field.empty? && match.count(@quote_and_newlines).zero? csv << (match.empty? ? nil : match) elsif(current_field.empty? ? match[0] : current_field[0]) == @quote_char[0] current_field << match field_quotes += match.count(@quote_char) if field_quotes % 2 == 0 in_quotes = current_field[@parsers[:quoted_field], 1] raise MalformedCSVError unless in_quotes current_field = in_quotes current_field.gsub!(@quote_char * 2, @quote_char) # unescape contents csv << current_field current_field = String.new field_quotes = 0 else # we found a quoted field that spans multiple lines current_field << @col_sep end elsif match.count("\r\n").zero? raise MalformedCSVError, "Illegal quoting on line #{lineno + 1}." else raise MalformedCSVError, "Unquoted fields do not allow " + "\\r or \\n (line #{lineno + 1})." end end # if parse is empty?(), we found all the fields on the line... if field_quotes % 2 == 0 @lineno += 1 # save fields unconverted fields, if needed... unconverted = csv.dup if @unconverted_fields # convert fields, if needed... csv = convert_fields(csv) unless @use_headers or @converters.empty? # parse out header rows and handle FasterCSV::Row conversions... csv = parse_headers(csv) if @use_headers # inject unconverted fields and accessor, if requested... if @unconverted_fields and not csv.respond_to? :unconverted_fields add_unconverted_fields(csv, unconverted) end # return the results break csv end # if we're not empty?() but at eof?(), a quoted field wasn't closed... if @io.eof? raise MalformedCSVError, "Unclosed quoted field on line #{lineno + 1}." elsif @field_size_limit and current_field.size >= @field_size_limit raise MalformedCSVError, "Field size exceeded on line #{lineno + 1}." end # otherwise, we need to loop and pull some more data to complete the row end end alias_method :gets, :shift alias_method :readline, :shift # Returns a simplified description of the key FasterCSV attributes. def inspect str = "<##{self.class} io_type:" # show type of wrapped IO if @io == $stdout then str << "$stdout" elsif @io == $stdin then str << "$stdin" elsif @io == $stderr then str << "$stderr" else str << @io.class.to_s end # show IO.path(), if available if @io.respond_to?(:path) and (p = @io.path) str << " io_path:#{p.inspect}" end # show other attributes %w[ lineno col_sep row_sep quote_char skip_blanks encoding ].each do |attr_name| if a = instance_variable_get("@#{attr_name}") str << " #{attr_name}:#{a.inspect}" end end if @use_headers str << " headers:#{(@headers || true).inspect}" end str << ">" end private # # Stores the indicated separators for later use. # # If auto-discovery was requested for @row_sep, this method will read # ahead in the @io and try to find one. +ARGF+, +STDIN+, +STDOUT+, # +STDERR+ and any stream open for output only with a default # @row_sep of $INPUT_RECORD_SEPARATOR ($/). # # This method also establishes the quoting rules used for CSV output. # def init_separators(options) # store the selected separators @col_sep = options.delete(:col_sep) @row_sep = options.delete(:row_sep) @quote_char = options.delete(:quote_char) @quote_and_newlines = "#{@quote_char}\r\n" if @quote_char.length != 1 raise ArgumentError, ":quote_char has to be a single character String" end # automatically discover row separator when requested if @row_sep == :auto if [ARGF, STDIN, STDOUT, STDERR].include?(@io) or (defined?(Zlib) and @io.class == Zlib::GzipWriter) @row_sep = $INPUT_RECORD_SEPARATOR else begin saved_pos = @io.pos # remember where we were while @row_sep == :auto # # if we run out of data, it's probably a single line # (use a sensible default) # if @io.eof? @row_sep = $INPUT_RECORD_SEPARATOR break end # read ahead a bit sample = @io.read(1024) sample += @io.read(1) if sample[-1..-1] == "\r" and not @io.eof? # try to find a standard separator if sample =~ /\r\n?|\n/ @row_sep = $& break end end # tricky seek() clone to work around GzipReader's lack of seek() @io.rewind # reset back to the remembered position while saved_pos > 1024 # avoid loading a lot of data into memory @io.read(1024) saved_pos -= 1024 end @io.read(saved_pos) if saved_pos.nonzero? rescue IOError # stream not opened for reading @row_sep = $INPUT_RECORD_SEPARATOR end end end # establish quoting rules do_quote = lambda do |field| @quote_char + String(field).gsub(@quote_char, @quote_char * 2) + @quote_char end @quote = if options.delete(:force_quotes) do_quote else lambda do |field| if field.nil? # represent +nil+ fields as empty unquoted fields "" else field = String(field) # Stringify fields # represent empty fields as empty quoted fields if field.empty? or field.count("\r\n#{@col_sep}#{@quote_char}").nonzero? do_quote.call(field) else field # unquoted field end end end end end # Pre-compiles parsers and stores them by name for access during reads. def init_parsers(options) # store the parser behaviors @skip_blanks = options.delete(:skip_blanks) @encoding = options.delete(:encoding) # nil will use $KCODE @field_size_limit = options.delete(:field_size_limit) # prebuild Regexps for faster parsing esc_col_sep = Regexp.escape(@col_sep) esc_row_sep = Regexp.escape(@row_sep) esc_quote = Regexp.escape(@quote_char) @parsers = { :any_field => Regexp.new( "[^#{esc_col_sep}]+", Regexp::MULTILINE, @encoding ), :quoted_field => Regexp.new( "^#{esc_quote}(.*)#{esc_quote}$", Regexp::MULTILINE, @encoding ), # safer than chomp!() :line_end => Regexp.new("#{esc_row_sep}\\z", nil, @encoding) } end # # Loads any converters requested during construction. # # If +field_name+ is set :converters (the default) field converters # are set. When +field_name+ is :header_converters header converters # are added instead. # # The :unconverted_fields option is also actived for # :converters calls, if requested. # def init_converters(options, field_name = :converters) if field_name == :converters @unconverted_fields = options.delete(:unconverted_fields) end instance_variable_set("@#{field_name}", Array.new) # find the correct method to add the coverters convert = method(field_name.to_s.sub(/ers\Z/, "")) # load converters unless options[field_name].nil? # allow a single converter not wrapped in an Array unless options[field_name].is_a? Array options[field_name] = [options[field_name]] end # load each converter... options[field_name].each do |converter| if converter.is_a? Proc # custom code block convert.call(&converter) else # by name convert.call(converter) end end end options.delete(field_name) end # Stores header row settings and loads header converters, if needed. def init_headers(options) @use_headers = options.delete(:headers) @return_headers = options.delete(:return_headers) @write_headers = options.delete(:write_headers) # headers must be delayed until shift(), in case they need a row of content @headers = nil init_converters(options, :header_converters) end # # The actual work method for adding converters, used by both # FasterCSV.convert() and FasterCSV.header_convert(). # # This method requires the +var_name+ of the instance variable to place the # converters in, the +const+ Hash to lookup named converters in, and the # normal parameters of the FasterCSV.convert() and FasterCSV.header_convert() # methods. # def add_converter(var_name, const, name = nil, &converter) if name.nil? # custom converter instance_variable_get("@#{var_name}") << converter else # named converter combo = const[name] case combo when Array # combo converter combo.each do |converter_name| add_converter(var_name, const, converter_name) end else # individual named converter instance_variable_get("@#{var_name}") << combo end end end # # Processes +fields+ with @converters, or @header_converters # if +headers+ is passed as +true+, returning the converted field set. Any # converter that changes the field into something other than a String halts # the pipeline of conversion for that field. This is primarily an efficiency # shortcut. # def convert_fields(fields, headers = false) # see if we are converting headers or fields converters = headers ? @header_converters : @converters fields.enum_for(:each_with_index).map do |field, index| # map_with_index converters.each do |converter| field = if converter.arity == 1 # straight field converter converter[field] else # FieldInfo converter header = @use_headers && !headers ? @headers[index] : nil converter[field, FieldInfo.new(index, lineno, header)] end break unless field.is_a? String # short-curcuit pipeline for speed end field # return final state of each field, converted or original end end # # This methods is used to turn a finished +row+ into a FasterCSV::Row. Header # rows are also dealt with here, either by returning a FasterCSV::Row with # identical headers and fields (save that the fields do not go through the # converters) or by reading past them to return a field row. Headers are also # saved in @headers for use in future rows. # # When +nil+, +row+ is assumed to be a header row not based on an actual row # of the stream. # def parse_headers(row = nil) if @headers.nil? # header row @headers = case @use_headers # save headers # Array of headers when Array then @use_headers # CSV header String when String self.class.parse_line( @use_headers, :col_sep => @col_sep, :row_sep => @row_sep, :quote_char => @quote_char ) # first row is headers else row end # prepare converted and unconverted copies row = @headers if row.nil? @headers = convert_fields(@headers, true) if @return_headers # return headers return FasterCSV::Row.new(@headers, row, true) elsif not [Array, String].include? @use_headers.class # skip to field row return shift end end FasterCSV::Row.new(@headers, convert_fields(row)) # field row end # # Thiw methods injects an instance variable unconverted_fields into # +row+ and an accessor method for it called unconverted_fields(). The # variable is set to the contents of +fields+. # def add_unconverted_fields(row, fields) class << row attr_reader :unconverted_fields end row.instance_eval { @unconverted_fields = fields } row end end # Another name for FasterCSV. FCSV = FasterCSV # Another name for FasterCSV::instance(). def FasterCSV(*args, &block) FasterCSV.instance(*args, &block) end # Another name for FCSV::instance(). def FCSV(*args, &block) FCSV.instance(*args, &block) end class Array # Equivalent to FasterCSV::generate_line(self, options). def to_csv(options = Hash.new) FasterCSV.generate_line(self, options) end end class String # Equivalent to FasterCSV::parse_line(self, options). def parse_csv(options = Hash.new) FasterCSV.parse_line(self, options) end end ================================================ FILE: lib/float.rb ================================================ class Float def round_to(x) (self * 10**x).round.to_f / 10**x end def ceil_to(x) (self * 10**x).ceil.to_f / 10**x end def floor_to(x) (self * 10**x).floor.to_f / 10**x end end ================================================ FILE: lib/generators/redmine_plugin/USAGE ================================================ Description: The plugin generator creates stubs for a new Redmine plugin. Example: ./script/generate redmine_plugin meetings create vendor/plugins/redmine_meetings/app/controllers create vendor/plugins/redmine_meetings/app/helpers create vendor/plugins/redmine_meetings/app/models create vendor/plugins/redmine_meetings/app/views create vendor/plugins/redmine_meetings/db/migrate create vendor/plugins/redmine_meetings/lib/tasks create vendor/plugins/redmine_meetings/assets/images create vendor/plugins/redmine_meetings/assets/javascripts create vendor/plugins/redmine_meetings/assets/stylesheets create vendor/plugins/redmine_meetings/lang create vendor/plugins/redmine_meetings/README create vendor/plugins/redmine_meetings/init.rb create vendor/plugins/redmine_meetings/lang/en.yml create vendor/plugins/redmine_meetings/config/locales/en.yml create vendor/plugins/redmine_meetings/test/test_helper.rb ================================================ FILE: lib/generators/redmine_plugin/redmine_plugin_generator.rb ================================================ class RedminePluginGenerator < Rails::Generator::NamedBase attr_reader :plugin_path, :plugin_name, :plugin_pretty_name def initialize(runtime_args, runtime_options = {}) super @plugin_name = "redmine_#{file_name.underscore}" @plugin_pretty_name = plugin_name.titleize @plugin_path = "vendor/plugins/#{plugin_name}" end def manifest record do |m| m.directory "#{plugin_path}/app/controllers" m.directory "#{plugin_path}/app/helpers" m.directory "#{plugin_path}/app/models" m.directory "#{plugin_path}/app/views" m.directory "#{plugin_path}/db/migrate" m.directory "#{plugin_path}/lib/tasks" m.directory "#{plugin_path}/assets/images" m.directory "#{plugin_path}/assets/javascripts" m.directory "#{plugin_path}/assets/stylesheets" m.directory "#{plugin_path}/lang" m.directory "#{plugin_path}/config/locales" m.directory "#{plugin_path}/test" m.template 'README.rdoc', "#{plugin_path}/README.rdoc" m.template 'init.rb.erb', "#{plugin_path}/init.rb" m.template 'en.yml', "#{plugin_path}/lang/en.yml" m.template 'en_rails_i18n.yml', "#{plugin_path}/config/locales/en.yml" m.template 'test_helper.rb.erb', "#{plugin_path}/test/test_helper.rb" end end end ================================================ FILE: lib/generators/redmine_plugin/templates/README.rdoc ================================================ = <%= file_name %> Description goes here ================================================ FILE: lib/generators/redmine_plugin/templates/en.yml ================================================ # English strings go here my_label: "My label" ================================================ FILE: lib/generators/redmine_plugin/templates/en_rails_i18n.yml ================================================ # English strings go here for Rails i18n en: my_label: "My label" ================================================ FILE: lib/generators/redmine_plugin/templates/init.rb.erb ================================================ require 'redmine' Redmine::Plugin.register :<%= plugin_name %> do name '<%= plugin_pretty_name %> plugin' author 'Author name' description 'This is a plugin for Redmine' version '0.0.1' end ================================================ FILE: lib/generators/redmine_plugin/templates/test_helper.rb.erb ================================================ # Load the normal Rails helper require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') # Ensure that we are using the temporary fixture path Engines::Testing.set_fixture_path ================================================ FILE: lib/generators/redmine_plugin_controller/USAGE ================================================ Description: Generates a plugin controller. Example: ./script/generate redmine_plugin_controller MyPlugin Pools index show vote ================================================ FILE: lib/generators/redmine_plugin_controller/redmine_plugin_controller_generator.rb ================================================ require 'rails_generator/base' require 'rails_generator/generators/components/controller/controller_generator' class RedminePluginControllerGenerator < ControllerGenerator attr_reader :plugin_path, :plugin_name, :plugin_pretty_name def initialize(runtime_args, runtime_options = {}) runtime_args = runtime_args.dup @plugin_name = "redmine_" + runtime_args.shift.underscore @plugin_pretty_name = plugin_name.titleize @plugin_path = "vendor/plugins/#{plugin_name}" super(runtime_args, runtime_options) end def destination_root File.join(RAILS_ROOT, plugin_path) end def manifest record do |m| # Check for class naming collisions. m.class_collisions class_path, "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper" # Controller, helper, views, and test directories. m.directory File.join('app/controllers', class_path) m.directory File.join('app/helpers', class_path) m.directory File.join('app/views', class_path, file_name) m.directory File.join('test/functional', class_path) # Controller class, functional test, and helper class. m.template 'controller.rb.erb', File.join('app/controllers', class_path, "#{file_name}_controller.rb") m.template 'functional_test.rb.erb', File.join('test/functional', class_path, "#{file_name}_controller_test.rb") m.template 'helper.rb.erb', File.join('app/helpers', class_path, "#{file_name}_helper.rb") # View template for each action. actions.each do |action| path = File.join('app/views', class_path, file_name, "#{action}.html.erb") m.template 'view.html.erb', path, :assigns => { :action => action, :path => path } end end end end ================================================ FILE: lib/generators/redmine_plugin_controller/templates/controller.rb.erb ================================================ class <%= class_name %>Controller < ApplicationController <% actions.each do |action| -%> def <%= action %> end <% end -%> end ================================================ FILE: lib/generators/redmine_plugin_controller/templates/functional_test.rb.erb ================================================ require File.dirname(__FILE__) + '/../test_helper' class <%= class_name %>ControllerTest < ActionController::TestCase # Replace this with your real tests. def test_truth assert true end end ================================================ FILE: lib/generators/redmine_plugin_controller/templates/helper.rb.erb ================================================ module <%= class_name %>Helper end ================================================ FILE: lib/generators/redmine_plugin_controller/templates/view.html.erb ================================================

    <%= class_name %>#<%= action %>

    ================================================ FILE: lib/generators/redmine_plugin_model/USAGE ================================================ Description: Generates a plugin model. Examples: ./script/generate redmine_plugin_model MyPlugin pool title:string question:text ================================================ FILE: lib/generators/redmine_plugin_model/redmine_plugin_model_generator.rb ================================================ require 'rails_generator/base' require 'rails_generator/generators/components/model/model_generator' class RedminePluginModelGenerator < ModelGenerator attr_accessor :plugin_path, :plugin_name, :plugin_pretty_name def initialize(runtime_args, runtime_options = {}) runtime_args = runtime_args.dup @plugin_name = "redmine_" + runtime_args.shift.underscore @plugin_pretty_name = plugin_name.titleize @plugin_path = "vendor/plugins/#{plugin_name}" super(runtime_args, runtime_options) end def destination_root File.join(RAILS_ROOT, plugin_path) end def manifest record do |m| # Check for class naming collisions. m.class_collisions class_path, class_name, "#{class_name}Test" # Model, test, and fixture directories. m.directory File.join('app/models', class_path) m.directory File.join('test/unit', class_path) m.directory File.join('test/fixtures', class_path) # Model class, unit test, and fixtures. m.template 'model.rb.erb', File.join('app/models', class_path, "#{file_name}.rb") m.template 'unit_test.rb.erb', File.join('test/unit', class_path, "#{file_name}_test.rb") unless options[:skip_fixture] m.template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml") end unless options[:skip_migration] m.migration_template 'migration.rb.erb', 'db/migrate', :assigns => { :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}" }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" end end end end ================================================ FILE: lib/generators/redmine_plugin_model/templates/fixtures.yml ================================================ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 <% for attribute in attributes -%> <%= attribute.name %>: <%= attribute.default %> <% end -%> two: id: 2 <% for attribute in attributes -%> <%= attribute.name %>: <%= attribute.default %> <% end -%> ================================================ FILE: lib/generators/redmine_plugin_model/templates/migration.rb.erb ================================================ class <%= migration_name %> < ActiveRecord::Migration def self.up create_table :<%= table_name %> do |t| <% for attribute in attributes -%> t.column :<%= attribute.name %>, :<%= attribute.type %> <% end -%> end end def self.down drop_table :<%= table_name %> end end ================================================ FILE: lib/generators/redmine_plugin_model/templates/model.rb.erb ================================================ class <%= class_name %> < ActiveRecord::Base end ================================================ FILE: lib/generators/redmine_plugin_model/templates/unit_test.rb.erb ================================================ require File.dirname(__FILE__) + '/../test_helper' class <%= class_name %>Test < ActiveSupport::TestCase fixtures :<%= table_name %> # Replace this with your real tests. def test_truth assert true end end ================================================ FILE: lib/mention.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# class Mention def self.parse(object, mentioner_id) #loop through properties and only search for mentions in text fields object.attributes.each_value do |text| next if text.class.to_s != 'String' text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(@)([a-zA-Z0-9._@]+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m| leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8 if esc.nil? if sep == '@' send_mention(object,mentioner_id,oid,text) end end end end end def self.send_mention(object,mentioner_id, mentioned_login, mention_text) #Find user or abort user = User.find_by_login(mentioned_login) return if user.nil? #Find better sub-section of text that includes user login mention_text_subsection = mention_text #Send mention to issue object.send(:mention,mentioner_id,user.id,mention_text_subsection) end end ================================================ FILE: lib/redcloth3.rb ================================================ # vim:ts=4:sw=4: # = RedCloth - Textile and Markdown Hybrid for Ruby # # Homepage:: http://whytheluckystiff.net/ruby/redcloth/ # Author:: why the lucky stiff (http://whytheluckystiff.net/) # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.) # License:: BSD # # (see http://hobix.com/textile/ for a Textile Reference.) # # Based on (and also inspired by) both: # # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt # Textism for PHP: http://www.textism.com/tools/textile/ # # # = RedCloth # # RedCloth is a Ruby library for converting Textile and/or Markdown # into HTML. You can use either format, intermingled or separately. # You can also extend RedCloth to honor your own custom text stylings. # # RedCloth users are encouraged to use Textile if they are generating # HTML and to use Markdown if others will be viewing the plain text. # # == What is Textile? # # Textile is a simple formatting style for text # documents, loosely based on some HTML conventions. # # == Sample Textile Text # # h2. This is a title # # h3. This is a subhead # # This is a bit of paragraph. # # bq. This is a blockquote. # # = Writing Textile # # A Textile document consists of paragraphs. Paragraphs # can be specially formatted by adding a small instruction # to the beginning of the paragraph. # # h[n]. Header of size [n]. # bq. Blockquote. # # Numeric list. # * Bulleted list. # # == Quick Phrase Modifiers # # Quick phrase modifiers are also included, to allow formatting # of small portions of text within a paragraph. # # \_emphasis\_ # \_\_italicized\_\_ # \*strong\* # \*\*bold\*\* # ??citation?? # -deleted text- # +inserted text+ # ^superscript^ # ~subscript~ # @code@ # %(classname)span% # # ==notextile== (leave text alone) # # == Links # # To make a hypertext link, put the link text in "quotation # marks" followed immediately by a colon and the URL of the link. # # Optional: text in (parentheses) following the link text, # but before the closing quotation mark, will become a Title # attribute for the link, visible as a tool tip when a cursor is above it. # # Example: # # "This is a link (This is a title) ":http://www.textism.com # # Will become: # # This is a link # # == Images # # To insert an image, put the URL for the image inside exclamation marks. # # Optional: text that immediately follows the URL in (parentheses) will # be used as the Alt text for the image. Images on the web should always # have descriptive Alt text for the benefit of readers using non-graphical # browsers. # # Optional: place a colon followed by a URL immediately after the # closing ! to make the image into a link. # # Example: # # !http://www.textism.com/common/textist.gif(Textist)! # # Will become: # # Textist # # With a link: # # !/common/textist.gif(Textist)!:http://textism.com # # Will become: # # Textist # # == Defining Acronyms # # HTML allows authors to define acronyms via the tag. The definition appears as a # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, # this should be used at least once for each acronym in documents where they appear. # # To quickly define an acronym in Textile, place the full text in (parentheses) # immediately following the acronym. # # Example: # # ACLU(American Civil Liberties Union) # # Will become: # # ACLU # # == Adding Tables # # In Textile, simple tables can be added by seperating each column by # a pipe. # # |a|simple|table|row| # |And|Another|table|row| # # Attributes are defined by style definitions in parentheses. # # table(border:1px solid black). # (background:#ddd;color:red). |{}| | | | # # == Using RedCloth # # RedCloth is simply an extension of the String class, which can handle # Textile formatting. Use it like a String and output HTML with its # RedCloth#to_html method. # # doc = RedCloth.new " # # h2. Test document # # Just a simple test." # # puts doc.to_html # # By default, RedCloth uses both Textile and Markdown formatting, with # Textile formatting taking precedence. If you want to turn off Markdown # formatting, to boost speed and limit the processor: # # class RedCloth::Textile.new( str ) class RedCloth3 < String VERSION = '3.0.4' DEFAULT_RULES = [:textile, :markdown] # # Two accessor for setting security restrictions. # # This is a nice thing if you're using RedCloth for # formatting in public places (e.g. Wikis) where you # don't want users to abuse HTML for bad things. # # If +:filter_html+ is set, HTML which wasn't # created by the Textile processor will be escaped. # # If +:filter_styles+ is set, it will also disable # the style markup specifier. ('{color: red}') # attr_accessor :filter_html, :filter_styles # # Accessor for toggling hard breaks. # # If +:hard_breaks+ is set, single newlines will # be converted to HTML break tags. This is the # default behavior for traditional RedCloth. # attr_accessor :hard_breaks # Accessor for toggling lite mode. # # In lite mode, block-level rules are ignored. This means # that tables, paragraphs, lists, and such aren't available. # Only the inline markup for bold, italics, entities and so on. # # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] ) # r.to_html # #=> "And then? She fell!" # attr_accessor :lite_mode # # Accessor for toggling span caps. # # Textile places `span' tags around capitalized # words by default, but this wreaks havoc on Wikis. # If +:no_span_caps+ is set, this will be # suppressed. # attr_accessor :no_span_caps # # Establishes the markup predence. Available rules include: # # == Textile Rules # # The following textile rules can be set individually. Or add the complete # set of rules with the single :textile rule, which supplies the rule set in # the following precedence: # # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/) # block_textile_table:: Textile table block structures # block_textile_lists:: Textile list structures # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.) # inline_textile_image:: Textile inline images # inline_textile_link:: Textile inline links # inline_textile_span:: Textile inline spans # glyphs_textile:: Textile entities (such as em-dashes and smart quotes) # # == Markdown # # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/) # block_markdown_setext:: Markdown setext headers # block_markdown_atx:: Markdown atx headers # block_markdown_rule:: Markdown horizontal rules # block_markdown_bq:: Markdown blockquotes # block_markdown_lists:: Markdown lists # inline_markdown_link:: Markdown links attr_accessor :rules # Returns a new RedCloth object, based on _string_ and # enforcing all the included _restrictions_. # # r = RedCloth.new( "h1. A bold man", [:filter_html] ) # r.to_html # #=>"

    A <b>bold</b> man

    " # def initialize( string, restrictions = [] ) restrictions.each { |r| method( "#{ r }=" ).call( true ) } super( string ) end # # Generates HTML from the Textile contents. # # r = RedCloth.new( "And then? She *fell*!" ) # r.to_html( true ) # #=>"And then? She fell!" # def to_html( *rules ) rules = DEFAULT_RULES if rules.empty? # make our working copy text = self.dup @urlrefs = {} @shelf = [] textile_rules = [:block_textile_table, :block_textile_lists, :block_textile_prefix, :inline_textile_image, :inline_textile_link, :inline_textile_code, :inline_textile_span, :glyphs_textile] markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule, :block_markdown_bq, :block_markdown_lists, :inline_markdown_reflink, :inline_markdown_link] @rules = rules.collect do |rule| case rule when :markdown markdown_rules when :textile textile_rules else rule end end.flatten # standard clean up incoming_entities text clean_white_space text # start processor @pre_list = [] rip_offtags text no_textile text escape_html_tags text hard_break text unless @lite_mode refs text # need to do this before text is split by #blocks block_textile_quotes text blocks text end inline text smooth_offtags text retrieve text text.gsub!( /<\/?notextile>/, '' ) text.gsub!( /x%x%/, '&' ) clean_html text if filter_html text.strip! text end ####### private ####### # # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. # (from PyTextile) # TEXTILE_TAGS = [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. collect! do |a, b| [a.chr, ( b.zero? and "" or "&#{ b };" )] end # # Regular expressions to convert to HTML. # A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ A_VLGN = /[\-^~]/ C_CLAS = '(?:\([^)]+\))' C_LNGE = '(?:\[[^\[\]]+\])' C_STYL = '(?:\{[^}]+\})' S_CSPN = '(?:\\\\\d+)' S_RSPN = '(?:/\d+)' A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ) PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' ) PUNCT_Q = Regexp::quote( '*-_+^~%' ) HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)' # Text markup tags, don't conflict with block tags SIMPLE_HTML_TAGS = [ 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br', 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo' ] QTAGS = [ ['**', 'b', :limit], ['*', 'strong', :limit], ['??', 'cite', :limit], ['-', 'del', :limit], ['__', 'i', :limit], ['_', 'em', :limit], ['%', 'span', :limit], ['+', 'ins', :limit], ['^', 'sup', :limit], ['~', 'sub', :limit] ] QTAGS.collect! do |rc, ht, rtype| rcq = Regexp::quote rc re = case rtype when :limit /(^|[>\s\(]) (#{rcq}) (#{C}) (?::(\S+?))? (\w|[^\s\-].*?[^\s\-]) #{rcq} (?=[[:punct:]]|\s|\)|$)/x else /(#{rcq}) (#{C}) (?::(\S+))? (\w|[^\s\-].*?[^\s\-]) #{rcq}/xm end [rc, ht, re, rtype] end # Elements to handle GLYPHS = [ # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing # [ /\'/, '‘' ], # single opening # [ //, '>' ], # greater-than # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing # [ /"/, '“' ], # double opening # [ /\b( )?\.{3}/, '\1…' ], # ellipsis # [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^\2\3', :no_span_caps ], # 3+ uppercase caps # [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash # [ /\s->\s/, ' → ' ], # right arrow # [ /\s-\s/, ' – ' ], # en dash # [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign # [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark # [ /\b ?[(\[]R[\])]/i, '®' ], # registered # [ /\b ?[(\[]C[\])]/i, '©' ] # copyright ] H_ALGN_VALS = { '<' => 'left', '=' => 'center', '>' => 'right', '<>' => 'justify' } V_ALGN_VALS = { '^' => 'top', '-' => 'middle', '~' => 'bottom' } # # Flexible HTML escaping # def htmlesc( str, mode=:Quotes ) if str str.gsub!( '&', '&' ) str.gsub!( '"', '"' ) if mode != :NoQuotes str.gsub!( "'", ''' ) if mode == :Quotes str.gsub!( '<', '<') str.gsub!( '>', '>') end str end # Search and replace for Textile glyphs (quotes, dashes, other symbols) def pgl( text ) #GLYPHS.each do |re, resub, tog| # next if tog and method( tog ).call # text.gsub! re, resub #end text.gsub!(/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/) do |m| "#{$1}" end end # Parses Textile attribute lists and builds an HTML attribute string def pba( text_in, element = "" ) return '' unless text_in style = [] text = text_in.dup if element == 'td' colspan = $1 if text =~ /\\(\d+)/ rowspan = $1 if text =~ /\/(\d+)/ style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN end style << "#{ htmlesc $1 };" if text.sub!( /\{([^}]*)\}/, '' ) && !filter_styles lang = $1 if text.sub!( /\[([^)]+?)\]/, '' ) cls = $1 if text.sub!( /\(([^()]+?)\)/, '' ) style << "padding-left:#{ $1.length }em;" if text.sub!( /([(]+)/, '' ) style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ atts = '' atts << " style=\"#{ style.join }\"" unless style.empty? atts << " class=\"#{ cls }\"" unless cls.to_s.empty? atts << " lang=\"#{ lang }\"" if lang atts << " id=\"#{ id }\"" if id atts << " colspan=\"#{ colspan }\"" if colspan atts << " rowspan=\"#{ rowspan }\"" if rowspan atts end TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m # Parses a Textile table block, building HTML from the result. def block_textile_table( text ) text.gsub!( TABLE_RE ) do |matches| tatts, fullrow = $~[1..2] tatts = pba( tatts, 'table' ) tatts = shelve( tatts ) if tatts rows = [] fullrow.each_line do |row| ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m cells = [] row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell| next if cell == '|' ctyp = 'd' ctyp = 'h' if cell =~ /^_/ catts = '' catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/ catts = shelve( catts ) if catts cells << "\t\t\t#{ cell }" end ratts = shelve( ratts ) if ratts rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" end "\t\n#{ rows.join( "\n" ) }\n\t\n\n" end end LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m # Parses Textile lists and generates HTML def block_textile_lists( text ) text.gsub!( LISTS_RE ) do |match| lines = match.split( /\n/ ) last_line = -1 depth = [] lines.each_with_index do |line, line_id| if line =~ LISTS_CONTENT_RE tl,atts,content = $~[1..3] if depth.last if depth.last.length > tl.length (depth.length - 1).downto(0) do |i| break if depth[i].length == tl.length lines[line_id - 1] << "\n\t\n\t" depth.pop end end if depth.last and depth.last.length == tl.length lines[line_id - 1] << '' end end unless depth.last == tl depth << tl atts = pba( atts ) atts = shelve( atts ) if atts lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t
  • #{ content }" else lines[line_id] = "\t\t
  • #{ content }" end last_line = line_id else last_line = line_id end if line_id - last_line > 1 or line_id == lines.length - 1 depth.delete_if do |v| lines[last_line] << "
  • \n\t" end end end lines.join( "\n" ) end end QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m def block_textile_quotes( text ) text.gsub!( QUOTES_RE ) do |match| lines = match.split( /\n/ ) quotes = '' indent = 0 lines.each do |line| line =~ QUOTES_CONTENT_RE bq,content = $1, $2 l = bq.count('>') if l != indent quotes << ("\n\n" + (l>indent ? '
    ' * (l-indent) : '
    ' * (indent-l)) + "\n\n") indent = l end quotes << (content + "\n") end quotes << ("\n" + '' * indent + "\n\n") quotes end end CODE_RE = /(\W) @ (?:\|(\w+?)\|)? (.+?) @ (?=\W)/x def inline_textile_code( text ) text.gsub!( CODE_RE ) do |m| before,lang,code,after = $~[1..4] lang = " lang=\"#{ lang }\"" if lang rip_offtags( "#{ before }#{ code }#{ after }" ) end end def lT( text ) text =~ /\#$/ ? 'o' : 'u' end def hard_break( text ) text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks end BLOCKS_GROUP_RE = /\n{2,}(?! )/m def blocks( text, deep_code = false ) text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk| plain = blk !~ /\A[#*> ]/ # skip blocks that are complex HTML if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1 blk else # search for indentation levels blk.strip! if blk.empty? blk else code_blk = nil blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk| flush_left iblk blocks iblk, plain iblk.gsub( /^(\S)/, "\t\\1" ) if plain code_blk = iblk; "" else iblk end end block_applied = 0 @rules.each do |rule_name| block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) ) end if block_applied.zero? if deep_code blk = "\t
    #{ blk }
    " else blk = "\t

    #{ blk }

    " end end # hard_break blk blk + "\n#{ code_blk }" end end end.join( "\n\n" ) ) end def textile_bq( tag, atts, cite, content ) cite, cite_title = check_refs( cite ) cite = " cite=\"#{ cite }\"" if cite atts = shelve( atts ) if atts "\t\n\t\t#{ content }

    \n\t" end def textile_p( tag, atts, cite, content ) atts = shelve( atts ) if atts "\t<#{ tag }#{ atts }>#{ content }" end alias textile_h1 textile_p alias textile_h2 textile_p alias textile_h3 textile_p alias textile_h4 textile_p alias textile_h5 textile_p alias textile_h6 textile_p def textile_fn_( tag, num, atts, cite, content ) atts << " id=\"fn#{ num }\" class=\"footnote\"" content = "#{ num } #{ content }" atts = shelve( atts ) if atts "\t#{ content }

    " end BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m def block_textile_prefix( text ) if text =~ BLOCK_RE tag,tagpre,num,atts,cite,content = $~[1..6] atts = pba( atts ) # pass to prefix handler if respond_to? "textile_#{ tag }", true text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) ) elsif respond_to? "textile_#{ tagpre }_", true text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) ) end end end SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m def block_markdown_setext( text ) if text =~ SETEXT_RE tag = if $2 == "="; "h1"; else; "h2"; end blk, cont = "<#{ tag }>#{ $1 }", $' blocks cont text.replace( blk + cont ) end end ATX_RE = /\A(\#{1,6}) # $1 = string of #'s [ ]* (.+?) # $2 = Header text [ ]* \#* # optional closing #'s (not counted) $/x def block_markdown_atx( text ) if text =~ ATX_RE tag = "h#{ $1.length }" blk, cont = "<#{ tag }>#{ $2 }\n\n", $' blocks cont text.replace( blk + cont ) end end MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m def block_markdown_bq( text ) text.gsub!( MARKDOWN_BQ_RE ) do |blk| blk.gsub!( /^ *> ?/, '' ) flush_left blk blocks blk blk.gsub!( /^(\S)/, "\t\\1" ) "
    \n#{ blk }\n
    \n\n" end end MARKDOWN_RULE_RE = /^(#{ ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) })$/ def block_markdown_rule( text ) text.gsub!( MARKDOWN_RULE_RE ) do |blk| "
    " end end # XXX TODO XXX def block_markdown_lists( text ) end def inline_textile_span( text ) QTAGS.each do |qtag_rc, ht, qtag_re, rtype| text.gsub!( qtag_re ) do |m| case rtype when :limit sta,qtag,atts,cite,content = $~[1..5] else qtag,atts,cite,content = $~[1..4] sta = '' end atts = pba( atts ) atts << " cite=\"#{ cite }\"" if cite atts = shelve( atts ) if atts "#{ sta }<#{ ht }#{ atts }>#{ content }" end end end LINK_RE = / ( ([\s\[{(]|[#{PUNCT}])? # $pre " # start (#{C}) # $atts ([^"\n]+?) # $text \s? (?:\(([^)]+?)\)(?="))? # $title ": ( # $url (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto [\w\/]\S+? ) (\/)? # $slash ([^\w\=\/;\(\)]*?) # $post ) (?=<|\s|$) /x #" def inline_textile_link( text ) text.gsub!( LINK_RE ) do |m| all,pre,atts,text,title,url,proto,slash,post = $~[1..9] if text.include?('
    ') all else url, url_title = check_refs( url ) title ||= url_title # Idea below : an URL with unbalanced parethesis and # ending by ')' is put into external parenthesis if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) url=url[0..-2] # discard closing parenth from url post = ")"+post # add closing parenth to post end atts = pba( atts ) atts = " href=\"#{ url }#{ slash }\"#{ atts }" atts << " title=\"#{ htmlesc title }\"" if title atts = shelve( atts ) if atts external = (url =~ /^https?:\/\//) ? ' class="external"' : '' "#{ pre }#{ text }#{ post }" end end end MARKDOWN_REFLINK_RE = / \[([^\[\]]+)\] # $text [ ]? # opt. space (?:\n[ ]*)? # one optional newline followed by spaces \[(.*?)\] # $id /x def inline_markdown_reflink( text ) text.gsub!( MARKDOWN_REFLINK_RE ) do |m| text, id = $~[1..2] if id.empty? url, title = check_refs( text ) else url, title = check_refs( id ) end atts = " href=\"#{ url }\"" atts << " title=\"#{ title }\"" if title atts = shelve( atts ) "#{ text }" end end MARKDOWN_LINK_RE = / \[([^\[\]]+)\] # $text \( # open paren [ \t]* # opt space ? # $href [ \t]* # opt space (?: # whole title (['"]) # $quote (.*?) # $title \3 # matching quote )? # title is optional \) /x def inline_markdown_link( text ) text.gsub!( MARKDOWN_LINK_RE ) do |m| text, url, quote, title = $~[1..4] atts = " href=\"#{ url }\"" atts << " title=\"#{ title }\"" if title atts = shelve( atts ) "#{ text }" end end TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/ MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m def refs( text ) @rules.each do |rule_name| method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/ end end def refs_textile( text ) text.gsub!( TEXTILE_REFS_RE ) do |m| flag, url = $~[2..3] @urlrefs[flag.downcase] = [url, nil] nil end end def refs_markdown( text ) text.gsub!( MARKDOWN_REFS_RE ) do |m| flag, url = $~[2..3] title = $~[6] @urlrefs[flag.downcase] = [url, title] nil end end def check_refs( text ) ret = @urlrefs[text.downcase] if text ret || [text, nil] end IMAGE_RE = / (>|\s|^) # start of line? \! # opening (\<|\=|\>)? # optional alignment atts (#{C}) # optional style,class atts (?:\. )? # optional dot-space ([^\s(!]+?) # presume this is the src \s? # optional space (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title \! # closing (?::#{ HYPERLINK })? # optional href /x def inline_textile_image( text ) text.gsub!( IMAGE_RE ) do |m| stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8] htmlesc title atts = pba( atts ) atts = " src=\"#{ url }\"#{ atts }" atts << " title=\"#{ title }\"" if title atts << " alt=\"#{ title }\"" # size = @getimagesize($url); # if($size) $atts.= " $size[3]"; href, alt_title = check_refs( href ) if href url, url_title = check_refs( url ) out = '' out << "" if href out << "" out << "#{ href_a1 }#{ href_a2 }" if href if algn algn = h_align( algn ) if stln == "

    " out = "

    #{ out }" else out = "#{ stln }

    #{ out }
    " end else out = stln + out end out end end def shelve( val ) @shelf << val " :redsh##{ @shelf.length }:" end def retrieve( text ) @shelf.each_with_index do |r, i| text.gsub!( " :redsh##{ i + 1 }:", r ) end end def incoming_entities( text ) ## turn any incoming ampersands into a dummy character for now. ## This uses a negative lookahead for alphanumerics followed by a semicolon, ## implying an incoming html entity, to be skipped text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) end def no_textile( text ) text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/, '\1\2\3' ) text.gsub!( /^ *==([^=]+.*?)==/m, '\1\2\3' ) end def clean_white_space( text ) # normalize line breaks text.gsub!( /\r\n/, "\n" ) text.gsub!( /\r/, "\n" ) text.gsub!( /\t/, ' ' ) text.gsub!( /^ +$/, '' ) text.gsub!( /\n{3,}/, "\n\n" ) text.gsub!( /"$/, "\" " ) # if entire document is indented, flush # to the left side flush_left text end def flush_left( text ) indt = 0 if text =~ /^ / while text !~ /^ {#{indt}}\S/ indt += 1 end unless text.empty? if indt.nonzero? text.gsub!( /^ {#{indt}}/, '' ) end end end def footnote_ref( text ) text.gsub!( /\b\[([0-9]+?)\](\s)?/, '\1\2' ) end OFFTAGS = /(code|pre|kbd|notextile)/ OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi OFFTAG_OPEN = /<#{ OFFTAGS }/ OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m def glyphs_textile( text, level = 0 ) if text !~ HASTAG_MATCH pgl text footnote_ref text else codepre = 0 text.gsub!( ALLTAG_MATCH ) do |line| ## matches are off if we're between ,
     etc.
                    if $1
                        if line =~ OFFTAG_OPEN
                            codepre += 1
                        elsif line =~ OFFTAG_CLOSE
                            codepre -= 1
                            codepre = 0 if codepre < 0
                        end
                    elsif codepre.zero?
                        glyphs_textile( line, level + 1 )
                    else
                        htmlesc( line, :NoQuotes )
                    end
                    # p [level, codepre, line]
    
                    line
                end
            end
        end
    
        def rip_offtags( text )
            if text =~ /<.*>/
                ## strip and encode 
     content
                codepre, used_offtags = 0, {}
                text.gsub!( OFFTAG_MATCH ) do |line|
                    if $3
                        offtag, aftertag = $4, $5
                        codepre += 1
                        used_offtags[offtag] = true
                        if codepre - used_offtags.length > 0
                            htmlesc( line, :NoQuotes )
                            @pre_list.last << line
                            line = ""
                        else
                            htmlesc( aftertag, :NoQuotes ) if aftertag
                            line = ""
                            $3.match(/<#{ OFFTAGS }([^>]*)>/)
                            tag = $1
                            $2.to_s.match(/(class\=\S+)/i)
                            tag << " #{$1}" if $1
                            @pre_list << "<#{ tag }>#{ aftertag }"
                        end
                    elsif $1 and codepre > 0
                        if codepre - used_offtags.length > 0
                            htmlesc( line, :NoQuotes )
                            @pre_list.last << line
                            line = ""
                        end
                        codepre -= 1 unless codepre.zero?
                        used_offtags = {} if codepre.zero?
                    end
                    line
                end
            end
            text
        end
    
        def smooth_offtags( text )
            unless @pre_list.empty?
                ## replace 
     content
                text.gsub!( // ) { @pre_list[$1.to_i] }
            end
        end
    
        def inline( text )
            [/^inline_/, /^glyphs_/].each do |meth_re|
                @rules.each do |rule_name|
                    method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
                end
            end
        end
    
        def h_align( text )
            H_ALGN_VALS[text]
        end
    
        def v_align( text )
            V_ALGN_VALS[text]
        end
    
        def textile_popup_help( name, windowW, windowH )
            ' ' + name + '
    ' end # HTML cleansing stuff BASIC_TAGS = { 'a' => ['href', 'title'], 'img' => ['src', 'alt', 'title'], 'br' => [], 'i' => nil, 'u' => nil, 'b' => nil, 'pre' => nil, 'kbd' => nil, 'code' => ['lang'], 'cite' => nil, 'strong' => nil, 'em' => nil, 'ins' => nil, 'sup' => nil, 'sub' => nil, 'del' => nil, 'table' => nil, 'tr' => nil, 'td' => ['colspan', 'rowspan'], 'th' => nil, 'ol' => nil, 'ul' => nil, 'li' => nil, 'p' => nil, 'h1' => nil, 'h2' => nil, 'h3' => nil, 'h4' => nil, 'h5' => nil, 'h6' => nil, 'blockquote' => ['cite'] } def clean_html( text, tags = BASIC_TAGS ) text.gsub!( /]*)>/ ) do raw = $~ tag = raw[2].downcase if tags.has_key? tag pcs = [tag] tags[tag].each do |prop| ['"', "'", ''].each do |q| q2 = ( q != '' ? q : '\s' ) if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i attrv = $1 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" break end end end if tags[tag] "<#{raw[1]}#{pcs.join " "}>" else " " end end end ALLOWED_TAGS = %w(redpre pre code notextile) def escape_html_tags(text) text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" } end end ================================================ FILE: lib/redmine/about.rb ================================================ module Redmine class About def self.print_plugin_info plugins = Redmine::Plugin.registered_plugins if !plugins.empty? column_with = plugins.map {|internal_name, plugin| plugin.name.length}.max puts "\nAbout your Redmine plugins" plugins.each do |internal_name, plugin| puts sprintf("%-#{column_with}s %s", plugin.name, plugin.version) end end end end end ================================================ FILE: lib/redmine/access_control.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module AccessControl class << self def map mapper = Mapper.new yield mapper @permissions ||= [] @permissions += mapper.mapped_permissions end def permissions @permissions end # Returns the permission of given name or nil if it wasn't found # Argument should be a symbol def permission(name) permissions.detect {|p| p.name == name} end # Returns the actions that are allowed by the permission of given name def allowed_actions(permission_name) perm = permission(permission_name) perm ? perm.actions : [] end def public_permissions @public_permissions ||= @permissions.select {|p| p.public?} end def members_only_permissions @members_only_permissions ||= @permissions.select {|p| p.require_member?} end def loggedin_only_permissions @loggedin_only_permissions ||= @permissions.select {|p| p.require_loggedin?} end def available_project_modules @available_project_modules ||= @permissions.collect(&:project_module).uniq.compact end def modules_permissions(modules) @permissions.select {|p| p.project_module.nil? || modules.include?(p.project_module.to_s)} end end class Mapper def initialize @project_module = nil end def permission(name, hash, options={}) @permissions ||= [] options.merge!(:project_module => @project_module) @permissions << Permission.new(name, hash, options) end def project_module(name, options={}) @project_module = name yield self @project_module = nil end def mapped_permissions @permissions end end class Permission attr_reader :name, :actions, :project_module def initialize(name, hash, options) @name = name @actions = [] @public = options[:public] || false @require = options[:require] @project_module = options[:project_module] hash.each do |controller, actions| if actions.is_a? Array @actions << actions.collect {|action| "#{controller}/#{action}"} else @actions << "#{controller}/#{actions}" end end @actions.flatten! end def public? @public end def require_member? @require && @require == :member end def require_loggedin? @require && (@require == :member || @require == :loggedin) end end end end ================================================ FILE: lib/redmine/access_keys.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # module Redmine module AccessKeys ACCESSKEYS = {:edit => 'e', :preview => 'r', :quick_search => 'f', :search => '4', :new_issue => '7' }.freeze unless const_defined?(:ACCESSKEYS) def self.key_for(action) ACCESSKEYS[action] end end end ================================================ FILE: lib/redmine/activity/fetcher.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module Activity # Class used to retrieve activity events class Fetcher attr_reader :user, :project, :scope # Needs to be unloaded in development mode @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } def initialize(user, options={}) options.assert_valid_keys(:project, :with_subprojects, :author) @user = user @project = options[:project] @options = options @scope = event_types end # Returns an array of available event types def event_types return @event_types unless @event_types.nil? @event_types = Redmine::Activity.available_event_types @event_types = @event_types.select {|o| @user.allowed_to?("view_#{o}".to_sym, @project)} if @project @event_types end # Yields to filter the activity scope def scope_select(&block) @scope = @scope.select {|t| yield t } end # Sets the scope # Argument can be :all, :default or an array of event types def scope=(s) case s when :all @scope = event_types when :default default_scope! else @scope = s & event_types end end # Resets the scope to the default scope def default_scope! @scope = Redmine::Activity.default_event_types end # Returns an array of events for the given date range # sorted in reverse chronological order def events(from = nil, to = nil, options={}) e = [] @options[:limit] = options[:limit] @scope.each do |event_type| constantized_providers(event_type).each do |provider| e += provider.find_events(event_type, @user, from, to, @options) end end e.sort! {|a,b| b.event_datetime <=> a.event_datetime} if options[:limit] e = e.slice(0, options[:limit]) end e end private def constantized_providers(event_type) @@constantized_providers[event_type] end end end end ================================================ FILE: lib/redmine/activity.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module Activity mattr_accessor :available_event_types, :default_event_types, :providers @@available_event_types = [] @@default_event_types = [] @@providers = Hash.new {|h,k| h[k]=[] } class << self def map(&block) yield self end # Registers an activity provider def register(event_type, options={}) options.assert_valid_keys(:class_name, :default) event_type = event_type.to_s providers = options[:class_name] || event_type.classify providers = ([] << providers) unless providers.is_a?(Array) @@available_event_types << event_type unless @@available_event_types.include?(event_type) @@default_event_types << event_type unless options[:default] == false @@providers[event_type] += providers end end end end ================================================ FILE: lib/redmine/core_ext/string/conversions.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2008 Shereef Bishay # module Redmine #:nodoc: module CoreExtensions #:nodoc: module String #:nodoc: # Custom string conversions module Conversions # Parses hours format and returns a float def to_hours s = self.dup s.strip! if s =~ %r{^(\d+([.,]\d+)?)h?$} s = $1 else # 2:30 => 2.5 s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 } # 2h30, 2h, 30m => 2.5, 2, 0.5 s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] } end # 2,5 => 2.5 s.gsub!(',', '.') begin; Kernel.Float(s); rescue; nil; end end # Object#to_a removed in ruby1.9 if RUBY_VERSION > '1.9' def to_a [self.dup] end end end end end end ================================================ FILE: lib/redmine/core_ext/string/inflections.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# module Redmine #:nodoc: module CoreExtensions #:nodoc: module String #:nodoc: # Custom string inflections module Inflections def with_leading_slash starts_with?('/') ? self : "/#{ self }" end end end end end ================================================ FILE: lib/redmine/core_ext/string.rb ================================================ require File.dirname(__FILE__) + '/string/conversions' require File.dirname(__FILE__) + '/string/inflections' class String #:nodoc: include Redmine::CoreExtensions::String::Conversions include Redmine::CoreExtensions::String::Inflections end ================================================ FILE: lib/redmine/core_ext.rb ================================================ Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each { |file| require(file) } ================================================ FILE: lib/redmine/default_data/loader.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module DefaultData class DataAlreadyLoaded < Exception; end module Loader include Redmine::I18n class << self # Returns true if no data is already loaded in the database # otherwise false def no_data? !Role.find(:first, :conditions => {:builtin => 0}) && !Tracker.find(:first) && !IssueStatus.find(:first) && !Enumeration.find(:first) end # Loads the default data # Raises a RecordNotSaved exception if something goes wrong def load(lang=nil) raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data? set_language_if_valid(lang) Role.transaction do # Roles administrator = Role.create! :name => l(:default_role_administrator), :position => 1, :builtin => Role::BUILTIN_ADMINISTRATOR, :scope => Role::LEVEL_PROJECT administrator.permissions = administrator.setable_permissions.collect {|p| p.name} administrator.permissions.delete(:edit_time_entries) administrator.permissions.delete(:manage_members) administrator.save! citizen = Role.create! :name => l(:default_role_citizen), :position => 2, :builtin => Role::BUILTIN_CORE_MEMBER, :scope => Role::LEVEL_PROJECT citizen.permissions = citizen.setable_permissions.collect {|p| p.name} citizen.permissions.delete(:add_project) citizen.permissions.delete(:edit_project) citizen.permissions.delete(:select_projected_modules) citizen.permissions.delete(:manage_members) citizen.permissions.delete(:manage_versions) citizen.permissions.delete(:edit_time_entries) citizen.save! contributor = Role.create! :name => l(:default_role_contributor), :position => 3, :builtin => Role::BUILTIN_CONTRIBUTOR, :scope => Role::LEVEL_PROJECT contributor.permissions = contributor.setable_permissions.collect {|p| p.name} contributor.permissions.delete(:add_project) contributor.permissions.delete(:edit_project) contributor.permissions.delete(:select_projected_modules) contributor.permissions.delete(:manage_members) contributor.permissions.delete(:manage_versions) contributor.permissions.delete(:manage_boards) contributor.permissions.delete(:edit_messages) contributor.permissions.delete(:delete_messages) contributor.permissions.delete(:delete_own_messages) contributor.permissions.delete(:manage_documents) contributor.permissions.delete(:manage_files) contributor.permissions.delete(:manage_categories) contributor.permissions.delete(:manage_issue_relations) contributor.permissions.delete(:edit_issue_notes) contributor.permissions.delete(:move_issues) contributor.permissions.delete(:delete_issues) contributor.permissions.delete(:push_commitment) contributor.permissions.delete(:manage_public_queries) contributor.permissions.delete(:add_issue_watchers) contributor.permissions.delete(:manage_news) contributor.permissions.delete(:edit_time_entries) contributor.permissions.delete(:manage_wiki) contributor.permissions.delete(:rename_wiki_pages) contributor.permissions.delete(:delete_wiki_pages) contributor.permissions.delete(:delete_wiki_pages_attachments) contributor.permissions.delete(:protect_wiki_pages) contributor.save! #TODO: Check that built in role aren't in there before creating them @nonmember = Role.new(:name => 'Non member', :position => 0) @nonmember.builtin = Role::BUILTIN_NON_MEMBER @nonmember.save @anonymous = Role.new(:name => 'Anonymous', :position => 0) @anonymous.builtin = Role::BUILTIN_ANONYMOUS @anonymous.save Role.non_member.update_attribute :permissions, contributor.permissions Role.anonymous.update_attribute :permissions, [:view_issues, :view_gantt, :view_calendar, :view_time_entries, :view_documents, :view_wiki_pages, :view_wiki_edits, :view_files, :view_changesets] # Trackers Tracker.create!(:name => l(:default_tracker_task), :is_in_chlog => true, :is_in_roadmap => true, :position => 1) Tracker.create!(:name => l(:default_tracker_subtask), :is_in_chlog => true, :is_in_roadmap => true, :position => 2) # Issue statuses new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1) assigned = IssueStatus.create!(:name => l(:default_issue_status_assigned), :is_closed => false, :is_default => false, :position => 2) closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 3) blocked = IssueStatus.create!(:name => l(:default_issue_status_blocked), :is_closed => false, :is_default => false, :position => 4) # Workflow Tracker.find(:all).each { |t| IssueStatus.find(:all).each { |os| IssueStatus.find(:all).each { |ns| Workflow.create!(:tracker_id => t.id, :role_id => administrator.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns } } } Tracker.find(:all).each { |t| IssueStatus.find(:all).each { |os| IssueStatus.find(:all).each { |ns| Workflow.create!(:tracker_id => t.id, :role_id => citizen.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns } } } Tracker.find(:all).each { |t| IssueStatus.find(:all).each { |os| IssueStatus.find(:all).each { |ns| Workflow.create!(:tracker_id => t.id, :role_id => contributor.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns } } } # Enumerations IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_low), :position => 1) IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_normal), :position => 2, :is_default => true) IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_high), :position => 3) IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_urgent), :position => 4) end true end end end end end ================================================ FILE: lib/redmine/export/pdf.rb ================================================ # encoding: utf-8 # # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# require 'iconv' require 'rfpdf/fpdf' require 'rfpdf/chinese' module Redmine module Export module PDF include ActionView::Helpers::TextHelper include ActionView::Helpers::NumberHelper class IFPDF < FPDF include Redmine::I18n attr_accessor :footer_date def initialize(lang) super() set_language_if_valid lang case current_language.to_s.downcase when 'ja' extend(PDF_Japanese) AddSJISFont() @font_for_content = 'SJIS' @font_for_footer = 'SJIS' when 'zh' extend(PDF_Chinese) AddGBFont() @font_for_content = 'GB' @font_for_footer = 'GB' when 'zh-tw' extend(PDF_Chinese) AddBig5Font() @font_for_content = 'Big5' @font_for_footer = 'Big5' else @font_for_content = 'Arial' @font_for_footer = 'Helvetica' end SetCreator(Redmine::Info.app_name) SetFont(@font_for_content) end def SetFontStyle(style, size) SetFont(@font_for_content, style, size) end def SetTitle(txt) txt = begin utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) hextxt = "" rescue txt end || '' super(txt) end def textstring(s) # Format a text string if s =~ /^ [:user, :details], :order => "#{Journal.table_name}.created_at ASC") pdf.SetFontStyle('B',8) pdf.Cell(190,5, format_time(journal.created_at) + " - " + journal.user.name) pdf.Ln pdf.SetFontStyle('I',8) for detail in journal.details pdf.Cell(190,5, "- " + show_detail(detail, true)) pdf.Ln end if journal.notes? pdf.SetFontStyle('',8) pdf.MultiCell(190,5, journal.notes) end pdf.Ln end if issue.attachments.any? pdf.SetFontStyle('B',9) pdf.Cell(190,5, l(:label_attachment_plural), "B") pdf.Ln for attachment in issue.attachments pdf.SetFontStyle('',8) pdf.Cell(80,5, attachment.filename) pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R") pdf.Cell(25,5, format_date(attachment.created_at),0,0,"R") pdf.Cell(65,5, attachment.author.name,0,0,"R") pdf.Ln end end pdf.Output end # Returns a PDF string of a gantt chart def gantt_to_pdf(gantt, project) pdf = IFPDF.new(current_language) pdf.SetTitle("#{l(:label_gantt)} #{project}") pdf.AliasNbPages pdf.footer_date = format_date(Date.today) pdf.AddPage("L") pdf.SetFontStyle('B',12) pdf.SetX(15) pdf.Cell(70, 20, project.to_s) pdf.Ln pdf.SetFontStyle('B',9) subject_width = 70 header_heigth = 5 headers_heigth = header_heigth show_weeks = false show_days = false if gantt.months < 7 show_weeks = true headers_heigth = 2*header_heigth if gantt.months < 3 show_days = true headers_heigth = 3*header_heigth end end g_width = 210 zoom = (g_width) / (gantt.date_to - gantt.date_from + 1) g_height = 120 t_height = g_height + headers_heigth y_start = pdf.GetY # Months headers month_f = gantt.date_from left = subject_width height = header_heigth gantt.months.times do width = ((month_f >> 1) - month_f) * zoom pdf.SetY(y_start) pdf.SetX(left) pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") left = left + width month_f = month_f >> 1 end # Weeks headers if show_weeks left = subject_width height = header_heigth if gantt.date_from.cwday == 1 # gantt.date_from is monday week_f = gantt.date_from else # find next monday after gantt.date_from week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1) width = (7 - gantt.date_from.cwday + 1) * zoom-1 pdf.SetY(y_start + header_heigth) pdf.SetX(left) pdf.Cell(width + 1, height, "", "LTR") left = left + width+1 end while week_f <= gantt.date_to width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom pdf.SetY(y_start + header_heigth) pdf.SetX(left) pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") left = left + width week_f = week_f+7 end end # Days headers if show_days left = subject_width height = header_heigth wday = gantt.date_from.cwday pdf.SetFontStyle('B',7) (gantt.date_to - gantt.date_from + 1).to_i.times do width = zoom pdf.SetY(y_start + 2 * header_heigth) pdf.SetX(left) pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") left = left + width wday = wday + 1 wday = 1 if wday > 7 end end pdf.SetY(y_start) pdf.SetX(15) pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) # Tasks top = headers_heigth + y_start pdf.SetFontStyle('B',7) gantt.events.each do |i| pdf.SetY(top) pdf.SetX(15) if i.is_a? Issue pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR") else pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR") end pdf.SetY(top) pdf.SetX(subject_width) pdf.Cell(g_width, 5, "", "LR") pdf.SetY(top+1.5) if i.is_a? Issue i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from ) i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to ) i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date ) i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date ) i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today i_left = ((i_start_date - gantt.date_from)*zoom) i_width = ((i_end_date - i_start_date + 1)*zoom) d_width = ((i_done_date - i_start_date)*zoom) l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date l_width ||= 0 pdf.SetX(subject_width + i_left) pdf.SetFillColor(200,200,200) pdf.Cell(i_width, 2, "", 0, 0, "", 1) if l_width > 0 pdf.SetY(top+1.5) pdf.SetX(subject_width + i_left) pdf.SetFillColor(255,100,100) pdf.Cell(l_width, 2, "", 0, 0, "", 1) end if d_width > 0 pdf.SetY(top+1.5) pdf.SetX(subject_width + i_left) pdf.SetFillColor(100,100,255) pdf.Cell(d_width, 2, "", 0, 0, "", 1) end pdf.SetY(top+1.5) pdf.SetX(subject_width + i_left + i_width) pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%") else i_left = ((i.start_date - gantt.date_from)*zoom) pdf.SetX(subject_width + i_left) pdf.SetFillColor(50,200,50) pdf.Cell(2, 2, "", 0, 0, "", 1) pdf.SetY(top+1.5) pdf.SetX(subject_width + i_left + 3) pdf.Cell(30, 2, "#{i.name}") end top = top + 5 pdf.SetDrawColor(200, 200, 200) pdf.Line(15, top, subject_width+g_width, top) if pdf.GetY() > 180 pdf.AddPage("L") top = 20 pdf.Line(15, top, subject_width+g_width, top) end pdf.SetDrawColor(0, 0, 0) end pdf.Line(15, top, subject_width+g_width, top) pdf.Output end end end end ================================================ FILE: lib/redmine/helpers/calendar.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module Helpers # Simple class to compute the start and end dates of a calendar class Calendar include Redmine::I18n attr_reader :startdt, :enddt def initialize(date, lang = current_language, period = :month) @date = date @events = [] @ending_events_by_days = {} @starting_events_by_days = {} set_language_if_valid lang case period when :month @startdt = Date.civil(date.year, date.month, 1) @enddt = (@startdt >> 1)-1 # starts from the first day of the week @startdt = @startdt - (@startdt.cwday - first_wday)%7 # ends on the last day of the week @enddt = @enddt + (last_wday - @enddt.cwday)%7 when :week @startdt = date - (date.cwday - first_wday)%7 @enddt = date + (last_wday - date.cwday)%7 else raise 'Invalid period' end end # Sets calendar events def events=(events) @events = events @ending_events_by_days = @events.group_by {|event| event.due_date} @starting_events_by_days = @events.group_by {|event| event.start_date} end # Returns events for the given day def events_on(day) ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq end # Calendar current month def month @date.month end # Return the first day of week # 1 = Monday ... 7 = Sunday def first_wday case Setting.start_of_week.to_i when 1 @first_dow ||= (1 - 1)%7 + 1 when 7 @first_dow ||= (7 - 1)%7 + 1 else @first_dow ||= (l(:general_first_day_of_week).to_i - 1)%7 + 1 end end def last_wday @last_dow ||= (first_wday + 5)%7 + 1 end end end end ================================================ FILE: lib/redmine/helpers/gantt.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module Helpers # Simple class to handle gantt chart data class Gantt attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :events def initialize(options={}) options = options.dup @events = [] if options[:year] && options[:year].to_i >0 @year_from = options[:year].to_i if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12 @month_from = options[:month].to_i else @month_from = 1 end else @month_from ||= Date.today.month @year_from ||= Date.today.year end zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i @zoom = (zoom > 0 && zoom < 5) ? zoom : 2 months = (options[:months] || User.current.pref[:gantt_months]).to_i @months = (months > 0 && months < 25) ? months : 6 # Save gantt parameters as user preference (zoom and months count) if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months])) User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months User.current.preference.save end @date_from = Date.civil(@year_from, @month_from, 1) @date_to = (@date_from >> @months) - 1 end def events=(e) @events = e.sort {|x,y| x.start_date <=> y.start_date } end def params { :zoom => zoom, :year => year_from, :month => month_from, :months => months } end def params_previous { :year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months } end def params_next { :year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months } end # Generates a gantt image # Only defined if RMagick is avalaible def to_image(format='PNG') date_to = (@date_from >> @months)-1 show_weeks = @zoom > 1 show_days = @zoom > 2 subject_width = 320 header_heigth = 18 # width of one day in pixels zoom = @zoom*2 g_width = (@date_to - @date_from + 1)*zoom g_height = 20 * events.length + 20 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth) height = g_height + headers_heigth imgl = Magick::ImageList.new imgl.new_image(subject_width+g_width+1, height) gc = Magick::Draw.new # Subjects top = headers_heigth + 20 gc.fill('black') gc.stroke('transparent') gc.stroke_width(1) events.each do |i| gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name)) top = top + 20 end # Months headers month_f = @date_from left = subject_width @months.times do width = ((month_f >> 1) - month_f) * zoom gc.fill('white') gc.stroke('grey') gc.stroke_width(1) gc.rectangle(left, 0, left + width, height) gc.fill('black') gc.stroke('transparent') gc.stroke_width(1) gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}") left = left + width month_f = month_f >> 1 end # Weeks headers if show_weeks left = subject_width height = header_heigth if @date_from.cwday == 1 # date_from is monday week_f = date_from else # find next monday after date_from week_f = @date_from + (7 - @date_from.cwday + 1) width = (7 - @date_from.cwday + 1) * zoom gc.fill('white') gc.stroke('grey') gc.stroke_width(1) gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1) left = left + width end while week_f <= date_to width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom gc.fill('white') gc.stroke('grey') gc.stroke_width(1) gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1) gc.fill('black') gc.stroke('transparent') gc.stroke_width(1) gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s) left = left + width week_f = week_f+7 end end # Days details (week-end in grey) if show_days left = subject_width height = g_height + header_heigth - 1 wday = @date_from.cwday (date_to - @date_from + 1).to_i.times do width = zoom gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white') gc.stroke('grey') gc.stroke_width(1) gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1) left = left + width wday = wday + 1 wday = 1 if wday > 7 end end # border gc.fill('transparent') gc.stroke('grey') gc.stroke_width(1) gc.rectangle(0, 0, subject_width+g_width, headers_heigth) gc.stroke('black') gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1) # content top = headers_heigth + 20 gc.stroke('transparent') events.each do |i| if i.is_a?(Issue) i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from ) i_end_date = (i.due_before <= date_to ? i.due_before : date_to ) i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) i_done_date = (i_done_date >= date_to ? date_to : i_done_date ) i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today i_left = subject_width + ((i_start_date - @date_from)*zoom).floor i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue d_width = ((i_done_date - i_start_date)*zoom).floor # done width l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width gc.fill('grey') gc.rectangle(i_left, top, i_left + i_width, top - 6) gc.fill('red') gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0 gc.fill('blue') gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0 gc.fill('black') gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%") else i_left = subject_width + ((i.start_date - @date_from)*zoom).floor gc.fill('green') gc.rectangle(i_left, top, i_left + 6, top - 6) gc.fill('black') gc.text(i_left + 11, top + 1, i.name) end top = top + 20 end # today red line if Date.today >= @date_from and Date.today <= date_to gc.stroke('red') x = (Date.today-@date_from+1)*zoom + subject_width gc.line(x, headers_heigth, x, headers_heigth + g_height-1) end gc.draw(imgl) imgl.format = format imgl.to_blob end if Object.const_defined?(:Magick) end end end ================================================ FILE: lib/redmine/i18n.rb ================================================ module Redmine module I18n def self.included(base) base.extend Redmine::I18n end def l(*args) case args.size when 1 ::I18n.t(*args) when 2 if args.last.is_a?(Hash) ::I18n.t(*args) elsif args.last.is_a?(String) ::I18n.t(args.first, :value => args.last) else ::I18n.t(args.first, :count => args.last) end else raise "Translation string with multiple values: #{args.first}" end end def l_or_humanize(s, options={}) k = "#{options[:prefix]}#{s}".to_sym ::I18n.t(k, :default => s.to_s.humanize) end def l_hours(hours) hours = hours.to_f l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => ("%.2f" % hours.to_f)) end def ll(lang, str, value=nil) ::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" }) end def format_date(date) return nil unless date Setting.date_format.blank? ? l(date.to_date) : date.strftime(Setting.date_format) end def format_time(time, include_date = true) return nil unless time time = time.to_time if time.is_a?(String) zone = User.current.time_zone local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time) Setting.time_format.blank? ? l(local, :format => (include_date ? :default : :time)) : ((include_date ? "#{format_date(time)} " : "") + "#{local.strftime(Setting.time_format)}") end def local_time(time) return nil unless time time = time.to_time if time.is_a?(String) zone = User.current.time_zone local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time) return local end def day_name(day) ::I18n.t('date.day_names')[day % 7] end def month_name(month) ::I18n.t('date.month_names')[month] end def valid_languages @@valid_languages ||= Dir.glob(File.join(RAILS_ROOT, 'config', 'locales', '*.yml')).collect {|f| File.basename(f).split('.').first}.collect(&:to_sym) end def find_language(lang) @@languages_lookup = valid_languages.inject({}) {|k, v| k[v.to_s.downcase] = v; k } @@languages_lookup[lang.to_s.downcase] end def set_language_if_valid(lang) if l = find_language(lang) ::I18n.locale = l end end def current_language ::I18n.locale end end end ================================================ FILE: lib/redmine/imap.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # require 'net/imap' module Redmine module IMAP class << self def check(imap_options={}, options={}) host = imap_options[:host] || '127.0.0.1' port = imap_options[:port] || '143' ssl = !imap_options[:ssl].nil? folder = imap_options[:folder] || 'INBOX' imap = Net::IMAP.new(host, port, ssl) imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil? imap.select(folder) imap.search(['NOT', 'SEEN']).each do |message_id| msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] logger.debug "Receiving message #{message_id}" if logger && logger.debug? if MailHandler.receive(msg, options) logger.debug "Message #{message_id} successfully received" if logger && logger.debug? if imap_options[:move_on_success] imap.copy(message_id, imap_options[:move_on_success]) end imap.store(message_id, "+FLAGS", [:Seen, :Deleted]) else logger.debug "Message #{message_id} can not be processed" if logger && logger.debug? imap.store(message_id, "+FLAGS", [:Seen]) if imap_options[:move_on_failure] imap.copy(message_id, imap_options[:move_on_failure]) imap.store(message_id, "+FLAGS", [:Deleted]) end end end imap.expunge end private def logger RAILS_DEFAULT_LOGGER end end end end ================================================ FILE: lib/redmine/info.rb ================================================ module Redmine module Info class << self def app_name; 'Redmine' end def url; 'http://www.redmine.org/' end def help_url; 'http://www.redmine.org/guide' end def versioned_name; "#{app_name} #{Redmine::VERSION}" end # Creates the url string to a specific Redmine issue def issue(issue_id) url + 'issues/' + issue_id.to_s end end end end ================================================ FILE: lib/redmine/menu_manager.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'tree' # gem install rubytree # Monkey patch the TreeNode to add on a few more methods :nodoc: module TreeNodePatch def self.included(base) base.class_eval do attr_reader :last_items_count alias :old_initilize :initialize def initialize(name, content = nil) old_initilize(name, content) @last_items_count = 0 @childrenHash ||= {} extend(InstanceMethods) end end end module InstanceMethods # Adds the specified child node to the receiver node. The child node's # parent is set to be the receiver. The child is added as the first child in # the current list of children for the receiver node. def prepend(child) raise "Child already added" if @childrenHash && @childrenHash.has_key?(child.name) @childrenHash[child.name] = child @children = [child] + @children child.parent = self return child end # Adds the specified child node to the receiver node. The child node's # parent is set to be the receiver. The child is added at the position # into the current list of children for the receiver node. def add_at(child, position) raise "Child already added" if @childrenHash && @childrenHash.has_key?(child.name) @childrenHash[child.name] = child @children = @children.insert(position, child) child.parent = self return child end def add_last(child) raise "Child already added" if @childrenHash && @childrenHash.has_key?(child.name) @childrenHash[child.name] = child @children << child @last_items_count += 1 child.parent = self return child end # Adds the specified child node to the receiver node. The child node's # parent is set to be the receiver. The child is added as the last child in # the current list of children for the receiver node. def add(child) raise "Child already added" if @childrenHash && @childrenHash.has_key?(child.name) @childrenHash[child.name] = child position = @children.size - @last_items_count @children.insert(position, child) child.parent = self return child end # Will return the position (zero-based) of the current child in # it's parent def position self.parent.children.index(self) end end end Tree::TreeNode.send(:include, TreeNodePatch) module Redmine module MenuManager class MenuError < StandardError #:nodoc: end module MenuController def self.included(base) base.extend(ClassMethods) end module ClassMethods @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}} mattr_accessor :menu_items # Set the menu item name for a controller or specific actions # Examples: # * menu_item :tickets # => sets the menu name to :tickets for the whole controller # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only # # The default menu item name for a controller is controller_name by default # Eg. the default menu item name for ProjectsController is :projects def menu_item(id, options = {}) if actions = options[:only] actions = [] << actions unless actions.is_a?(Array) actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id} else menu_items[controller_name.to_sym][:default] = id end end end def menu_items self.class.menu_items end # Returns the menu item name according to the current action def current_menu_item @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] || menu_items[controller_name.to_sym][:default] end # Redirects user to the menu item of the given project # Returns false if user is not authorized def redirect_to_project_menu_item(project, name) item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s} if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project)) redirect_to({item.param => project}.merge(item.url)) return true end false end end module MenuHelper # Returns the current menu item name def current_menu_item @controller.current_menu_item end # Renders the application main menu def render_main_menu(project) render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project) end def render_menu(menu, project=nil) links = [] menu_items_for(menu, project) do |node| links << render_menu_node(node, project) end links.empty? ? nil : content_tag('ul', links.join("\n")) end def render_menu_node(node, project=nil) if node.hasChildren? || !node.child_menus.nil? return render_menu_node_with_children(node, project) else caption, url, selected = extract_node_details(node, project) return content_tag('li', render_single_menu_node(node, caption, url, selected)) end end def render_menu_node_with_children(node, project=nil) caption, url, selected = extract_node_details(node, project) html = returning [] do |html| html << '
  • ' # Parent html << render_single_menu_node(node, caption, url, selected) # Standard children standard_children_list = returning "" do |child_html| node.children.each do |child| child_html << render_menu_node(child, project) end end html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty? # Unattached children unattached_children_list = render_unattached_children_menu(node, project) html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank? html << '
  • ' end return html.join("\n") end # Returns a list of unattached children menu items def render_unattached_children_menu(node, project) return nil unless node.child_menus returning "" do |child_html| unattached_children = node.child_menus.call(project) # Tree nodes support #each so we need to do object detection if unattached_children.is_a? Array unattached_children.each do |child| child_html << content_tag(:li, render_unattached_menu_item(child, project)) end else raise MenuError, ":child_menus must be an array of MenuItems" end end end def render_single_menu_node(item, caption, url, selected) link_to(h(caption), url, item.html_options(:selected => selected)) end def render_unattached_menu_item(menu_item, project) raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem if User.current.allowed_to?(menu_item.url, project) link_to(h(menu_item.caption), menu_item.url, menu_item.html_options) end end def menu_items_for(menu, project=nil) items = [] Redmine::MenuManager.items(menu).root.children.each do |node| if allowed_node?(node, User.current, project) if block_given? yield node else items << node # TODO: not used? end end end return block_given? ? nil : items end def extract_node_details(node, project=nil) item = node url = case item.url when Hash project.nil? ? item.url : {item.param => project}.merge(item.url) when Symbol send(item.url) else item.url end caption = item.caption(project) return [caption, url, (current_menu_item == item.name)] end # Checks if a user is allowed to access the menu item by: # # * Checking the conditions of the item # * Checking the url target (project only) def allowed_node?(node, user, project) if node.condition && !node.condition.call(project) # Condition that doesn't pass return false end if project return user && user.allowed_to?(node.url, project) else # outside a project, all menu items allowed return true end end end class << self def map(menu_name) @items ||= {} mapper = Mapper.new(menu_name.to_sym, @items) if block_given? yield mapper else mapper end end def items(menu_name) @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {}) end end class Mapper def initialize(menu, items) items[menu] ||= Tree::TreeNode.new(:root, {}) @menu = menu @menu_items = items[menu] end @@last_items_count = Hash.new {|h,k| h[k] = 0} # Adds an item at the end of the menu. Available options: # * param: the parameter name that is used for the project id (default is :id) # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true # * caption that can be: # * a localized string Symbol # * a String # * a Proc that can take the project as argument # * before, after: specify where the menu item should be inserted (eg. :after => :activity) # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues) # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item. # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] } # * last: menu item will stay at the end (eg. :last => true) # * html_options: a hash of html options that are passed to link_to def push(name, url, options={}) options = options.dup if options[:parent] subtree = self.find(options[:parent]) if subtree target_root = subtree else target_root = @menu_items.root end else target_root = @menu_items.root end # menu item position if first = options.delete(:first) target_root.prepend(MenuItem.new(name, url, options)) elsif before = options.delete(:before) if exists?(before) target_root.add_at(MenuItem.new(name, url, options), position_of(before)) else target_root.add(MenuItem.new(name, url, options)) end elsif after = options.delete(:after) if exists?(after) target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1) else target_root.add(MenuItem.new(name, url, options)) end elsif options.delete(:last) target_root.add_last(MenuItem.new(name, url, options)) else target_root.add(MenuItem.new(name, url, options)) end end # Removes a menu item def delete(name) if found = self.find(name) @menu_items.remove!(found) end end # Checks if a menu item exists def exists?(name) @menu_items.any? {|node| node.name == name} end def find(name) @menu_items.find {|node| node.name == name} end def position_of(name) @menu_items.each do |node| if node.name == name return node.position end end end end class MenuItem < Tree::TreeNode include Redmine::I18n attr_reader :name, :url, :param, :condition, :parent, :child_menus def initialize(name, url, options) raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash) raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call) @name = name @url = url @condition = options[:if] @param = options[:param] || :id @caption = options[:caption] @html_options = options[:html] || {} # Adds a unique class to each menu item based on its name @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ') @parent = options[:parent] @child_menus = options[:children] super @name.to_sym end def caption(project=nil) if @caption.is_a?(Proc) c = @caption.call(project).to_s c = @name.to_s.humanize if c.blank? c else if @caption.nil? l_or_humanize(name, :prefix => 'label_') else @caption.is_a?(Symbol) ? l(@caption) : @caption end end end def html_options(options={}) if options[:selected] o = @html_options.dup o[:class] += ' gt-active' o else @html_options end end end end end ================================================ FILE: lib/redmine/mime_type.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module MimeType MIME_TYPES = { 'text/plain' => 'txt,tpl,properties,patch,diff,ini,readme,install,upgrade', 'text/css' => 'css', 'text/html' => 'html,htm,xhtml', 'text/jsp' => 'jsp', 'text/x-c' => 'c,cpp,cc,h,hh', 'text/x-csharp' => 'cs', 'text/x-java' => 'java', 'text/x-javascript' => 'js', 'text/x-html-template' => 'rhtml,html.erb', 'text/x-perl' => 'pl,pm', 'text/x-php' => 'php,php3,php4,php5', 'text/x-python' => 'py', 'text/x-ruby' => 'rb,rbw,ruby,rake,erb', 'text/x-csh' => 'csh', 'text/x-sh' => 'sh', 'text/xml' => 'xml,xsd,mxml', 'text/yaml' => 'yml,yaml', 'image/gif' => 'gif', 'image/jpeg' => 'jpg,jpeg,jpe', 'image/png' => 'png', 'image/tiff' => 'tiff,tif', 'image/x-ms-bmp' => 'bmp', 'image/x-xpixmap' => 'xpm', 'application/pdf' => 'pdf', 'application/zip' => 'zip', 'application/x-gzip' => 'gz', }.freeze EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)| exts.split(',').each {|ext| map[ext.strip] = type} map end # returns mime type for name or nil if unknown def self.of(name) return nil unless name m = name.to_s.match(/(^|\.)([^\.]+)$/) EXTENSIONS[m[2].downcase] if m end # Returns the css class associated to # the mime type of name def self.css_class_of(name) mime = of(name) mime && mime.gsub('/', '-') end def self.main_mimetype_of(name) mimetype = of(name) mimetype.split('/').first if mimetype end # return true if mime-type for name is type/* # otherwise false def self.is_type?(type, name) main_mimetype = main_mimetype_of(name) type.to_s == main_mimetype end end end ================================================ FILE: lib/redmine/platform.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module Platform class << self def mswin? (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) end end end end ================================================ FILE: lib/redmine/plugin.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine #:nodoc: class PluginNotFound < StandardError; end class PluginRequirementError < StandardError; end # Base class for Redmine plugins. # Plugins are registered using the register class method that acts as the public constructor. # # Redmine::Plugin.register :example do # name 'Example plugin' # author 'John Smith' # description 'This is an example plugin for Redmine' # version '0.0.1' # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' # end # # === Plugin attributes # # +settings+ is an optional attribute that let the plugin be configurable. # It must be a hash with the following keys: # * :default: default value for the plugin settings # * :partial: path of the configuration partial view, relative to the plugin app/views directory # Example: # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' # In this example, the settings partial will be found here in the plugin directory: app/views/settings/_settings.rhtml. # # When rendered, the plugin settings value is available as the local variable +settings+ class Plugin @registered_plugins = {} class << self attr_reader :registered_plugins private :new def def_field(*names) class_eval do names.each do |name| define_method(name) do |*args| args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args) end end end end end def_field :name, :description, :url, :author, :author_url, :version, :settings attr_reader :id # Plugin constructor def self.register(id, &block) p = new(id) p.instance_eval(&block) # Set a default name if it was not provided during registration p.name(id.to_s.humanize) if p.name.nil? # Adds plugin locales if any # YAML translation files should be found under /config/locales/ ::I18n.load_path += Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', id.to_s, 'config', 'locales', '*.yml')) registered_plugins[id] = p end # Returns an array off all registered plugins def self.all registered_plugins.values.sort end # Finds a plugin by its id # Returns a PluginNotFound exception if the plugin doesn't exist def self.find(id) registered_plugins[id.to_sym] || raise(PluginNotFound) end # Clears the registered plugins hash # It doesn't unload installed plugins def self.clear @registered_plugins = {} end def initialize(id) @id = id.to_sym end def <=>(plugin) self.id.to_s <=> plugin.id.to_s end # Sets a requirement on Redmine version # Raises a PluginRequirementError exception if the requirement is not met # # Examples # # Requires Redmine 0.7.3 or higher # requires_redmine :version_or_higher => '0.7.3' # requires_redmine '0.7.3' # # # Requires a specific Redmine version # requires_redmine :version => '0.7.3' # 0.7.3 only # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 def requires_redmine(arg) arg = { :version_or_higher => arg } unless arg.is_a?(Hash) arg.assert_valid_keys(:version, :version_or_higher) current = Redmine::VERSION.to_a arg.each do |k, v| v = [] << v unless v.is_a?(Array) versions = v.collect {|s| s.split('.').collect(&:to_i)} case k when :version_or_higher raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1 unless (current <=> versions.first) >= 0 raise PluginRequirementError.new("#{id} plugin requires Redmine #{v} or higher but current is #{current.join('.')}") end when :version unless versions.include?(current.slice(0,3)) raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{v.join(', ')} but current is #{current.join('.')}") end end end true end # Sets a requirement on a Redmine plugin version # Raises a PluginRequirementError exception if the requirement is not met # # Examples # # Requires a plugin named :foo version 0.7.3 or higher # requires_redmine_plugin :foo, :version_or_higher => '0.7.3' # requires_redmine_plugin :foo, '0.7.3' # # # Requires a specific version of a Redmine plugin # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 def requires_redmine_plugin(plugin_name, arg) arg = { :version_or_higher => arg } unless arg.is_a?(Hash) arg.assert_valid_keys(:version, :version_or_higher) plugin = Plugin.find(plugin_name) current = plugin.version.split('.').collect(&:to_i) arg.each do |k, v| v = [] << v unless v.is_a?(Array) versions = v.collect {|s| s.split('.').collect(&:to_i)} case k when :version_or_higher raise ArgumentError.new("wrong number of versions (#{versions.size} for 1)") unless versions.size == 1 unless (current <=> versions.first) >= 0 raise PluginRequirementError.new("#{id} plugin requires the #{plugin_name} plugin #{v} or higher but current is #{current.join('.')}") end when :version unless versions.include?(current.slice(0,3)) raise PluginRequirementError.new("#{id} plugin requires one the following versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}") end end end true end # Adds an item to the given +menu+. # The +id+ parameter (equals to the project id) is automatically added to the url. # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' # # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu # def menu(menu, item, url, options={}) Redmine::MenuManager.map(menu).push(item, url, options) end alias :add_menu_item :menu # Removes +item+ from the given +menu+. def delete_menu_item(menu, item) Redmine::MenuManager.map(menu).delete(item) end # Defines a permission called +name+ for the given +actions+. # # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array): # permission :destroy_contacts, { :contacts => :destroy } # permission :view_contacts, { :contacts => [:index, :show] } # # The +options+ argument can be used to make the permission public (implicitly given to any user) # or to restrict users the permission can be given to. # # Examples # # A permission that is implicitly given to any user # # This permission won't appear on the Roles & Permissions setup screen # permission :say_hello, { :example => :say_hello }, :public => true # # # A permission that can be given to any user # permission :say_hello, { :example => :say_hello } # # # A permission that can be given to registered users only # permission :say_hello, { :example => :say_hello }, :require => loggedin # # # A permission that can be given to project members only # permission :say_hello, { :example => :say_hello }, :require => member def permission(name, actions, options = {}) if @project_module Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}} else Redmine::AccessControl.map {|map| map.permission(name, actions, options)} end end # Defines a project module, that can be enabled/disabled for each project. # Permissions defined inside +block+ will be bind to the module. # # project_module :things do # permission :view_contacts, { :contacts => [:list, :show] }, :public => true # permission :destroy_contacts, { :contacts => :destroy } # end def project_module(name, &block) @project_module = name self.instance_eval(&block) @project_module = nil end # Registers an activity provider. # # Options: # * :class_name - one or more model(s) that provide these events (inferred from event_type by default) # * :default - setting this option to false will make the events not displayed by default # # A model can provide several activity event types. # # Examples: # register :news # register :scrums, :class_name => 'Meeting' # register :issues, :class_name => ['Issue', 'Journal'] # # Retrieving events: # Associated model(s) must implement the find_events class method. # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method. # # The following call should return all the scrum events visible by current user that occured in the 5 last days: # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today) # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only # # Note that :view_scrums permission is required to view these events in the activity view. def activity_provider(*args) Redmine::Activity.register(*args) end # Registers a wiki formatter. # # Parameters: # * +name+ - human-readable name # * +formatter+ - formatter class, which should have an instance method +to_html+ # * +helper+ - helper module, which will be included by wiki pages def wiki_format_provider(name, formatter, helper) Redmine::WikiFormatting.register(name, formatter, helper) end # Returns +true+ if the plugin can be configured. def configurable? settings && settings.is_a?(Hash) && !settings[:partial].blank? end end end ================================================ FILE: lib/redmine/search.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2009 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module Redmine module Search module Controller def self.included(base) base.extend(ClassMethods) end module ClassMethods @@default_search_scopes = Hash.new {|hash, key| hash[key] = {:default => nil, :actions => {}}} mattr_accessor :default_search_scopes # Set the default search scope for a controller or specific actions # Examples: # * search_scope :issues # => sets the search scope to :issues for the whole controller # * search_scope :issues, :only => :index # * search_scope :issues, :only => [:index, :show] def default_search_scope(id, options = {}) if actions = options[:only] actions = [] << actions unless actions.is_a?(Array) actions.each {|a| default_search_scopes[controller_name.to_sym][:actions][a.to_sym] = id.to_s} else default_search_scopes[controller_name.to_sym][:default] = id.to_s end end end def default_search_scopes self.class.default_search_scopes end # Returns the default search scope according to the current action def default_search_scope @default_search_scope ||= default_search_scopes[controller_name.to_sym][:actions][action_name.to_sym] || default_search_scopes[controller_name.to_sym][:default] end end end end ================================================ FILE: lib/redmine/themes.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module Themes # Return an array of installed themes def self.themes @@installed_themes ||= scan_themes end # Rescan themes directory def self.rescan @@installed_themes = scan_themes end # Return theme for given id, or nil if it's not found def self.theme(id) themes.find {|t| t.id == id} end # Class used to represent a theme class Theme attr_reader :name, :dir, :stylesheets def initialize(path) @dir = File.basename(path) @name = @dir.humanize @stylesheets = Dir.glob("#{path}/stylesheets/*.css").collect {|f| File.basename(f).gsub(/\.css$/, '')} end # Directory name used as the theme id def id; dir end def <=>(theme) name <=> theme.name end end private def self.scan_themes dirs = Dir.glob("#{RAILS_ROOT}/public/themes/*").select do |f| # A theme should at least override application.css File.directory?(f) && File.exist?("#{f}/stylesheets/application.css") end dirs.collect {|dir| Theme.new(dir)}.sort end end end module ApplicationHelper def stylesheet_path(source) @current_theme ||= Redmine::Themes.theme(Setting.ui_theme) super((@current_theme && @current_theme.stylesheets.include?(source)) ? "/themes/#{@current_theme.dir}/stylesheets/#{source}" : source) end def path_to_stylesheet(source) stylesheet_path source end end ================================================ FILE: lib/redmine/unified_diff.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # module Redmine # Class used to parse unified diffs class UnifiedDiff < Array def initialize(diff, options={}) options.assert_valid_keys(:type, :max_lines) diff = diff.split("\n") if diff.is_a?(String) diff_type = options[:type] || 'inline' lines = 0 @truncated = false diff_table = DiffTable.new(diff_type) diff.each do |line| unless diff_table.add_line line self << diff_table if diff_table.length > 1 diff_table = DiffTable.new(diff_type) end lines += 1 if options[:max_lines] && lines > options[:max_lines] @truncated = true break end end self << diff_table unless diff_table.empty? self end def truncated?; @truncated; end end # Class that represents a file diff class DiffTable < Hash attr_reader :file_name, :line_num_l, :line_num_r # Initialize with a Diff file and the type of Diff View # The type view must be inline or sbs (side_by_side) def initialize(type="inline") @parsing = false @nb_line = 1 @start = false @before = 'same' @second = true @type = type end # Function for add a line of this Diff # Returns false when the diff ends def add_line(line) unless @parsing if line =~ /^(---|\+\+\+) (.*)$/ @file_name = $2 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ @line_num_l = $2.to_i @line_num_r = $5.to_i @parsing = true end else if line =~ /^[^\+\-\s@\\]/ @parsing = false return false elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ @line_num_l = $2.to_i @line_num_r = $5.to_i else @nb_line += 1 if parse_line(line, @type) end end return true end def inspect puts '### DIFF TABLE ###' puts "file : #{file_name}" self.each do |d| d.inspect end end private # Test if is a Side By Side type def sbs?(type, func) if @start and type == "sbs" if @before == func and @second tmp_nb_line = @nb_line self[tmp_nb_line] = Diff.new else @second = false tmp_nb_line = @start @start += 1 @nb_line -= 1 end else tmp_nb_line = @nb_line @start = @nb_line self[tmp_nb_line] = Diff.new @second = true end unless self[tmp_nb_line] @nb_line += 1 self[tmp_nb_line] = Diff.new else self[tmp_nb_line] end end # Escape the HTML for the diff def escapeHTML(line) CGI.escapeHTML(line) end def parse_line(line, type="inline") if line[0, 1] == "+" diff = sbs? type, 'add' @before = 'add' diff.line_right = escapeHTML line[1..-1] diff.nb_line_right = @line_num_r diff.type_diff_right = 'diff_in' @line_num_r += 1 true elsif line[0, 1] == "-" diff = sbs? type, 'remove' @before = 'remove' diff.line_left = escapeHTML line[1..-1] diff.nb_line_left = @line_num_l diff.type_diff_left = 'diff_out' @line_num_l += 1 true elsif line[0, 1] =~ /\s/ @before = 'same' @start = false diff = Diff.new diff.line_right = escapeHTML line[1..-1] diff.nb_line_right = @line_num_r diff.line_left = escapeHTML line[1..-1] diff.nb_line_left = @line_num_l self[@nb_line] = diff @line_num_l += 1 @line_num_r += 1 true elsif line[0, 1] = "\\" true else false end end end # A line of diff class Diff attr_accessor :nb_line_left attr_accessor :line_left attr_accessor :nb_line_right attr_accessor :line_right attr_accessor :type_diff_right attr_accessor :type_diff_left def initialize() self.nb_line_left = '' self.nb_line_right = '' self.line_left = '' self.line_right = '' self.type_diff_right = '' self.type_diff_left = '' end def inspect puts '### Start Line Diff ###' puts self.nb_line_left puts self.line_left puts self.nb_line_right puts self.line_right end end end ================================================ FILE: lib/redmine/utils.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module Redmine module Utils class << self # Returns the relative root url of the application def relative_url_root ActionController::Base.respond_to?('relative_url_root') ? ActionController::Base.relative_url_root.to_s : ActionController::AbstractRequest.relative_url_root.to_s end # Sets the relative root url of the application def relative_url_root=(arg) if ActionController::Base.respond_to?('relative_url_root=') ActionController::Base.relative_url_root=arg else ActionController::AbstractRequest.relative_url_root=arg end end end end end ================================================ FILE: lib/redmine/version.rb ================================================ require 'rexml/document' module Redmine module VERSION #:nodoc: MAJOR = 0 MINOR = 9 TINY = 0 # Branch values: # * official release: nil # * stable branch: stable # * trunk: devel BRANCH = 'devel' def self.revision revision = nil entries_path = "#{RAILS_ROOT}/.svn/entries" if File.readable?(entries_path) begin f = File.open(entries_path, 'r') entries = f.read f.close if entries.match(%r{^\d+}) revision = $1.to_i if entries.match(%r{^\d+\s+dir\s+(\d+)\s}) else xml = REXML::Document.new(entries) revision = xml.elements['wc-entries'].elements[1].attributes['revision'].to_i end rescue # Could not find the current revision end end revision end REVISION = self.revision ARRAY = [MAJOR, MINOR, TINY, BRANCH, REVISION].compact STRING = ARRAY.join('.') def self.to_a; ARRAY end def self.to_s; STRING end end end ================================================ FILE: lib/redmine/views/my_page/block.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# module Redmine module Views module MyPage module Block def self.additional_blocks @@additional_blocks ||= Dir.glob("#{RAILS_ROOT}/vendor/plugins/*/app/views/my/blocks/_*.{rhtml,erb}").inject({}) do |h,file| name = File.basename(file).split('.').first.gsub(/^_/, '') h[name] = name.to_sym h end end end end end end ================================================ FILE: lib/redmine/views/other_formats_builder.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license# module Redmine module Views class OtherFormatsBuilder def initialize(view) @view = view end def link_to(name, options={}) url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {}) caption = options.delete(:caption) || name html_options = { :class => name.to_s.downcase, :rel => 'nofollow' }.merge(options) @view.content_tag('span', @view.link_to(caption, url, html_options)) end end end end ================================================ FILE: lib/redmine/wiki_formatting/macros.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# module Redmine module WikiFormatting module Macros module Definitions def exec_macro(name, obj, args) method_name = "macro_#{name}" send(method_name, obj, args) if respond_to?(method_name) end def extract_macro_options(args, *keys) options = {} while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym) options[$1.downcase.to_sym] = $2 args.pop end return [args, options] end end @@available_macros = {} class << self # Called with a block to define additional macros. # Macro blocks accept 2 arguments: # * obj: the object that is rendered # * args: macro arguments # # Plugins can use this method to define new macros: # # Redmine::WikiFormatting::Macros.register do # desc "This is my macro" # macro :my_macro do |obj, args| # "My macro output" # end # end def register(&block) class_eval(&block) if block_given? end private # Defines a new macro with the given name and block. def macro(name, &block) name = name.to_sym if name.is_a?(String) @@available_macros[name] = @@desc || '' @@desc = nil raise "Can not create a macro without a block!" unless block_given? Definitions.send :define_method, "macro_#{name}".downcase, &block end # Sets description for the next macro to be defined def desc(txt) @@desc = txt end end # Builtin macros desc "Sample macro." macro :hello_world do |obj, args| "Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}") end desc "Displays a list of all available macros, including description if available." macro :macro_list do out = '' @@available_macros.keys.collect(&:to_s).sort.each do |macro| out << content_tag('dt', content_tag('code', macro)) out << content_tag('dd', textilizable(@@available_macros[macro.to_sym])) end content_tag('dl', out) end desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" + " !{{child_pages}} -- can be used from a wiki page only\n" + " !{{child_pages(Foo)}} -- lists all children of page Foo\n" + " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo" macro :child_pages do |obj, args| args, options = extract_macro_options(args, :parent) page = nil if args.size > 0 page = Wiki.find_page(args.first.to_s, :project => @project) elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) page = obj.page else raise 'With no argument, this macro can be called from wiki pages only.' end raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) pages = ([page] + page.descendants).group_by(&:parent_id) render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id) end desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}" macro :include do |obj, args| page = Wiki.find_page(args.first.to_s, :project => @project) raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project) @included_wiki_pages ||= [] raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title) @included_wiki_pages << page.title out = textilizable(page.content, :text, :attachments => page.attachments) @included_wiki_pages.pop out end end end end ================================================ FILE: lib/redmine/wiki_formatting/textile/formatter.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # require 'redcloth3' require 'coderay' module Redmine module WikiFormatting module Textile class Formatter < RedCloth3 # auto_link rule after textile rules so that it doesn't break !image_url! tags RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros] def initialize(*args) super self.hard_breaks=true self.no_span_caps=true self.filter_styles=true end def to_html(*rules, &block) @toc = [] @macros_runner = block super(*RULES).to_s end private # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. # http://code.whytheluckystiff.net/redcloth/changeset/128 def hard_break( text ) text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks end # Patch to add code highlighting support to RedCloth def smooth_offtags( text ) unless @pre_list.empty? ## replace
     content
                text.gsub!(//) do
                  content = @pre_list[$1.to_i]
                  if content.match(/\s?(.+)/m)
                    content = "" +
                      CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
                  end
                  content
                end
              end
            end
    
            # Patch to add 'table of content' support to RedCloth
            def textile_p_withtoc(tag, atts, cite, content)
              # removes wiki links from the item
              toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
              # removes styles
              # eg. %{color:red}Triggers% => Triggers
              toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
    
              # replaces non word caracters by dashes
              anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
    
              unless anchor.blank?
                if tag =~ /^h(\d)$/
                  @toc << [$1.to_i, anchor, toc_item]
                end
                atts << " id=\"#{anchor}\""
                content = content + ""
              end
              textile_p(tag, atts, cite, content)
            end
    
            alias :textile_h1 :textile_p_withtoc
            alias :textile_h2 :textile_p_withtoc
            alias :textile_h3 :textile_p_withtoc
    
            def inline_toc(text)
              text.gsub!(/

    \{\{([<>]?)toc\}\}<\/p>/i) do div_class = 'toc' div_class << ' right' if $1 == '>' div_class << ' left' if $1 == '<' out = "

      " @toc.each do |heading| level, anchor, toc_item = heading out << "
    • #{toc_item}
    • \n" end out << '
    ' out end end MACROS_RE = / (!)? # escaping ( \{\{ # opening tag ([\w]+) # macro name (\(([^\}]*)\))? # optional arguments \}\} # closing tag ) /x unless const_defined?(:MACROS_RE) def inline_macros(text) text.gsub!(MACROS_RE) do esc, all, macro = $1, $2, $3.downcase args = ($5 || '').split(',').each(&:strip) if esc.nil? begin @macros_runner.call(macro, args) rescue => e "
    Error executing the #{macro} macro (#{e})
    " end || all else all end end end AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or [^=<>!:'"/]| # leading punctuation, or ^ # beginning of line ) ( (?:https?://)| # protocol spec, or (?:s?ftps?://)| (?:www\.) # www.* ) ( (\S+?) # url (\/)? # slash ) ([^\w\=\/;\(\)]*?) # post (?=<|\s|$) }x unless const_defined?(:AUTO_LINK_RE) # Turns all urls into clickable links (code from Rails). def inline_auto_link(text) text.gsub!(AUTO_LINK_RE) do all, leading, proto, url, post = $&, $1, $2, $3, $6 if leading =~ /=]?/ # don't replace URL's that are already linked # and URL's prefixed with ! !> !< != (textile images) all else # Idea below : an URL with unbalanced parethesis and # ending by ')' is put into external parenthesis if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) ) url=url[0..-2] # discard closing parenth from url post = ")"+post # add closing parenth to post end %(#{leading}#{proto + url}#{post}) end end end # Turns all email addresses into clickable links (code from Rails). def inline_auto_mailto(text) text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do mail = $1 if text.match(/]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) mail else %{} end end end end end end end ================================================ FILE: lib/redmine/wiki_formatting/textile/helper.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module WikiFormatting module Textile module Helper def initial_page_content(page) "h1. #{@page.pretty_title}" end end end end end ================================================ FILE: lib/redmine/wiki_formatting.rb ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # module Redmine module WikiFormatting @@formatters = {} class << self def map yield self end def register(name, formatter, helper) raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name.to_sym] @@formatters[name.to_sym] = {:formatter => formatter, :helper => helper} end def formatter_for(name) entry = @@formatters[name.to_sym] (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter end def helper_for(name) entry = @@formatters[name.to_sym] (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper end def format_names @@formatters.keys.map end def to_html(format, text, options = {}, &block) formatter_for(format).new(text).to_html(&block) end end # Default formatter module module NullFormatter class Formatter include ActionView::Helpers::TagHelper include ActionView::Helpers::TextHelper include ActionView::Helpers::UrlHelper def initialize(text) @text = text end def to_html(*args) simple_format(auto_link(CGI::escapeHTML(@text))) end end module Helper def initial_page_content(page) page.pretty_title.to_s end end end end end ================================================ FILE: lib/redmine.rb ================================================ require 'redmine/access_control' require 'redmine/menu_manager' require 'redmine/activity' require 'redmine/mime_type' require 'redmine/core_ext' require 'redmine/themes' require 'redmine/plugin' require 'redmine/wiki_formatting' require 'float' #todo: there's a more appropriate place for this require 'string' require 'mention' begin require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick) rescue LoadError # RMagick is not available end if RUBY_VERSION < '1.9' require 'faster_csv' else require 'csv' FCSV = CSV end REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem ) # Permissions Redmine::AccessControl.map do |map| map.permission :view_project, {:projects => [:overview, :activity, :team, :shares, :map, :activity, :mypris, :community_members, :hourly_types, :all_tags]}, :public => true map.permission :search_project, {:search => :index}, :public => true map.permission :add_project, {:projects => [:add, :new, :copy]}, :require => :loggedin map.permission :add_subprojects, {:projects => [:add, :new]}, :require => :loggedin map.permission :edit_project, {:projects => [:settings, :edit, :copy, :archive, :unarchive, :destroy, :update_scale]}, :require => :member map.permission :move_project, {:projects => [:move]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member map.permission :credits, {:credits => [:add, :edit, :update]}, :require => :admin map.permission :send_invitations, {:invitations => [:new, :create, :resend, :index], :projects => [:reset_invitation_token]}, :require => :loggedin map.permission :manage_invitations, {:invitations => [:index, :destroy, :resend, :update]}, :require => :loggedin map.permission :transfer_credits, {:credit_transfers => [:index, :create]}, :require => :loggedin map.permission :join_from_generic_invitation, {:projects => :join}, :require => :loggedin map.project_module :issue_tracking do |map| # Issues map.permission :view_issues, {:projects => :roadmap, :issues => [:index, :changes, :show, :context_menu, :datadump], :queries => :index, :reports => :issue_report, :comments => :index, :todos => :index, :retros => [:index, :index_json, :dashdata, :show], :projects => [:dashboard,:community_members, :dashdata, :new_dashdata] } map.permission :add_issues, {:issues => [:new, :update_form]} map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :update_form, :cancel, :restart, :prioritize, :agree, :disagree, :estimate, :join, :leave, :add_team_member, :remove_team_member, :update_tags]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} map.permission :add_issue_notes, {:issues => [:reply], :comments => :create, :todos => [:create,:update,:destroy]} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :move_issues, {:issues => :move}, :require => :loggedin map.permission :delete_issues, {:issues => :destroy}, :require => :member map.permission :push_commitment, {:issues => [:assign]} #Can send request for someone to comitt to a task map.permission :pull_commitment, {:issues => [:assign]} #Can pull request. i.e. ask to be the person that the task is commited to. map.permission :view_commit_requests, {:commit_requests => [:edit, :show]} #Can view ownereship requests map.permission :view_member_roles, {:member_roles => [:show]} #Can view member roles map.permission :estimate_issues, {:issues => :estimate}, :public => true #Can estimate issue map.permission :accept_issues, {:issues => [:accept, :reject]}, :public => true #can accept or reject issues map.permission :start_issues, {:issues => [:start,:finish,:release], :retro_ratings => :create} #can start issues # Queries map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin # Gantt & calendar map.permission :view_gantt, :issues => :gantt map.permission :view_calendar, :issues => :calendar # Watchers map.permission :view_issue_watchers, {} map.permission :add_issue_watchers, {:watchers => :new} map.permission :delete_issue_watchers, {:watchers => :destroy} end map.project_module :documents do |map| map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin map.permission :view_documents, :documents => [:index, :show, :download] map.permission :manage_files, {:projects => :add_file}, :require => :loggedin map.permission :view_files, :projects => :list_files end map.project_module :wiki do |map| map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member map.permission :view_wiki_pages, :wiki => [:index, :special] map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate] map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment] map.permission :delete_wiki_pages_attachments, {} map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member end map.project_module :boards do |map| map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true map.permission :add_messages, {:messages => [:new, :reply, :quote]} map.permission :edit_messages, {:messages => :edit}, :require => :member map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin map.permission :delete_messages, {:messages => :destroy}, :require => :member map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin end map.project_module :motions do |map| map.permission :manage_motion, {:motions => [:edit, :destroy]}, :require => :admin map.permission :browse_motion, {:motions => [:index, :view, :show]}, :require => :loggedin map.permission :create_motion, {:motions => [:create, :new, :eligible_users]}, :require => :loggedin map.permission :vote_motion, {:motions => :reply, :motion_vote => :create}, :require => :loggedin end map.project_module :news do |map| map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member map.permission :view_news, {:news => [:index, :show]}, :public => true map.permission :comment_news, {:news => :add_comment} end map.project_module :credits do |map| map.permission :view_credits, {:projects => :credits, :credits => [:index,:show]}, :require => :loggedin map.permission :enable_disable_credits, {:credits => [:enable, :disable]}, :require => :loggedin map.permission :add_credits, {:credits => [:new, :create]}, :require => :loggedin map.permission :manage_credits, {:credits => [:destroy, :edit, :update]}, :require => :loggedin end end Redmine::MenuManager.map :application_menu do |menu| # Empty end Redmine::MenuManager.map :admin_menu do |menu| # Empty end Redmine::MenuManager.map :project_menu do |menu| menu.push :overview, { :controller => 'projects', :action => 'overview' } menu.push :dashboard, { :controller => 'projects', :action => 'dashboard' }, :caption => :label_dashboard menu.push :team, { :controller => 'projects', :action => 'team' }, :if => Proc.new { |p| p.root? } menu.push :credits, { :controller => 'projects', :action => 'credits' }, :if => Proc.new { |p| p.credits_enabled? } menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :caption => :label_boards menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil } menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true end Redmine::Activity.map do |activity| activity.register :issues, :class_name => %w(Issue Journal) activity.register :news activity.register :documents, :class_name => %w(Document Attachment) activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => true activity.register :messages, :default => true end Redmine::WikiFormatting.map do |format| format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper end ================================================ FILE: lib/string.rb ================================================ require 'rexml/parsers/pullparser' class String def truncate_html(len = 30) begin p = REXML::Parsers::PullParser.new(self) tags = [] new_len = len results = '' while p.has_next? && new_len > 0 p_e = p.pull case p_e.event_type when :start_element tags.push p_e[0] results << "<#{tags.last} #{attrs_to_s(p_e[1])}>" when :end_element results << "" when :text results << p_e[0].first(new_len) new_len -= p_e[0].length else results << "" end end tags.reverse.each do |tag| results << "" end results rescue self end end private def attrs_to_s(attrs) if attrs.empty? '' else attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ') end end end ================================================ FILE: lib/tabular_form_builder.rb ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# require 'action_view/helpers/form_helper' class TabularFormBuilder < ActionView::Helpers::FormBuilder include Redmine::I18n def initialize(object_name, object, template, options, proc) set_language_if_valid options.delete(:lang) super end (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector| src = <<-END_SRC def #{selector}(field, options = {}) label_for_field(field, options) + super end END_SRC class_eval src, __FILE__, __LINE__ end def select(field, choices, options = {}, html_options = {}) label_for_field(field, options) + super end # Returns a label tag for the given field def label_for_field(field, options = {}) return '' if options.delete(:no_label) text = options[:label].is_a?(Symbol) ? l(options[:label]) : options[:label] text ||= l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) text += @template.content_tag("span", " *", :class => "required") if options.delete(:required) @template.content_tag("label", text, :class => (@object && @object.errors[field] ? "error" : nil), :for => (@object_name.to_s + "_" + field.to_s)) end end ================================================ FILE: lib/tasks/autoaccept_commitrequests.rake ================================================ # Auto accepts commitment requests that haven't been responded to MINIMUM_INCREMENT = 2 #Minimum size of time increment (in days) that can go buy before the system auto-accepts a request SECONDS_PER_DAY = 86400 def helpers ActionController::Base.helpers end desc "Auto accepts commitment requests that haven't been responded to" task :autoaccept_commitrequests => :environment do admin = User.find(:first,:conditions => {:login => "admin"}) CommitRequest.find(:all, :conditions => 'response = 0 AND responder_id is null AND days > -1').each do |cr| non_response_time = (Time.now - cr.created_at) / SECONDS_PER_DAY next if non_response_time < MINIMUM_INCREMENT if non_response_time > cr.days puts "Auto accepting: #{cr.id} for issue #{cr.issue.subject}" cr.response = 2 cr.responder_id = admin.id cr.save end end end ================================================ FILE: lib/tasks/backup.rake ================================================ # Heroku S3 Database backup task # by Nick Merwin (Lemur Heavy Industries) 10.08.09 # * dumps db to yaml, gzip's and sends to S3 # # Setup: # 1) replace APP_NAME and BACKUP_BUCKET with your info # 2) add config/s3.yml like so (same as Paperclip's): # production: # access_key_id: ... # secret_access_key: ... # 2) install the yaml_db plugin: # script/plugin install git://github.com/adamwiggins/yaml_db.git # 3) add aws-s3 to your .gems # # Usage: # heroku rake backup # rake backup remote=true # this will pull the db locally first # * or add this to your cron.rake for hourly or nightly backups: # Rake::Task['backup'].invoke require 'aws/s3' desc "backup db from heroku and send to S3" task :backup => :environment do APP_NAME = 'bettermeans' # put your app name here BACKUP_BUCKET = 'heroku-db-bckps-bm' # put your backup bucket name here puts "Back up started @ #{Time.now}" puts "Pulling DB..." backup_name = "ew#{Time.now.to_i}.db" backup_path = "tmp/#{backup_name}" if ENV['remote'] == 'true' puts `heroku db:pull sqlite://#{backup_path} --app #{APP_NAME}` else YamlDb.dump backup_path end puts "gzipping db..." `gzip #{backup_path}` backup_name += ".gz" backup_path = "tmp/#{backup_name}" puts "Uploading #{backup_name} to S3..." yaml_string = ERB.new(File.read("#{RAILS_ROOT}/config/s3.yml")).result options = YAML.load(yaml_string) puts "yaml string #{yaml_string}" puts "access key #{options[Rails.env]['access_key_id']}" puts "secret key #{options[Rails.env]['secret_access_key']}" AWS::S3::Base.establish_connection!( :access_key_id => options[Rails.env]['access_key_id'], :secret_access_key => options[Rails.env]['secret_access_key'] ) begin bucket = AWS::S3::Bucket.find BACKUP_BUCKET rescue AWS::S3::NoSuchBucket AWS::S3::Bucket.create BACKUP_BUCKET bucket = AWS::S3::Bucket.find BACKUP_BUCKET end AWS::S3::S3Object.store backup_name, File.read(backup_path), bucket.name, :content_type => 'application/x-gzip' puts "Done @ #{Time.now}" end namespace :heroku do desc "PostgreSQL database backups from Heroku to Amazon S3" task :daily_backup => :environment do begin require 'right_aws' puts "[#{Time.now}] heroku:backup started" name = "#{ENV['APP_NAME']}-#{Time.now.strftime('%Y-%m-%d-%H%M%S')}.dump" db = ENV['DATABASE_URL'].match(/postgres:\/\/([^:]+):([^@]+)@([^\/]+)\/(.+)/) system "PGPASSWORD=#{db[2]} pg_dump -Fc --username=#{db[1]} --host=#{db[3]} #{db[4]} > tmp/#{name}" s3 = RightAws::S3.new(ENV['s3_access_key_id'], ENV['s3_secret_access_key']) bucket = s3.bucket("#{ENV['APP_NAME']}-heroku-backups", true, 'private') bucket.put(name, open("tmp/#{name}")) system "rm tmp/#{name}" puts "[#{Time.now}] heroku:backup complete" end end end ================================================ FILE: lib/tasks/bootstrap.rake ================================================ task :bootstrap => :environment do puts "Creating db..." Rake::Task['db:create'].invoke puts "Loading schema..." Rake::Task['db:schema:load'].invoke puts "Seeding..." Rake::Task['db:seed'].invoke puts "Creating admin" user = User.new :firstname => "Redmine",:lastname => "Admin",:mail => "admin@example.net",:mail_notification => true,:language => "en",:status => 1 user.admin = true user.hashed_password = "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" #admin password is 'password' user.login = "admin" user.save end ================================================ FILE: lib/tasks/cleanup.rake ================================================ desc 'Remove trailing whitespace and add newlines at end of files' task :cleanup do filetypes = %w( builder conf css erb Gemfile haml html json rake rb rhtml rjs rxml scss js txt xml yaml yml ) paths = %w( app config curriculum/source db/migrate Gemfile lib modules public test spec ) filetype_regexp = /\.(#{filetypes.join('|')})$/ paths_regexp = /^(\.\/)?(#{paths.join('|')})/ files = `find .` files.lines.each do |f| # Only examine specified file types and paths if f =~ filetype_regexp && f =~ paths_regexp # Add a linebreak to the end of the file if it doesn't have one if `tail -c1 #{f}` != "\n" puts "adding line break to #{f}" `echo >> #{f}` end # Remove trailing whitespace if it exists if system("grep -q '[[:blank:]]$' #{f}") puts "removing trailing spaces from #{f}" # sed command works differently on Mac and Linux if `uname` =~ /Darwin/ `sed -i "" -e $'s/[ \t]*$//g' #{f}` elsif `uname` =~ /Linux/ `sed -i -e 's/[ \t]*$//g' #{f}` end end # sed command works differently on Mac and Linux if `uname` =~ /Darwin/ `sed -i "" -e $'s/\t/ /g' #{f}` elsif `uname` =~ /Linux/ `sed -i -e 's/\t/ /g' #{f}` end end end end ================================================ FILE: lib/tasks/close_retros.rake ================================================ # Closes retrospectives that are open def helpers ActionController::Base.helpers end desc "Closes retrospectives that are open" task :close_retros => :environment do puts "Closing retros..." Retro.find(:all, :conditions => {:status_id => Retro::STATUS_INPROGRESS}).each do |retro| if ((Time.now.advance(:days => Setting::DEFAULT_RETROSPECTIVE_LENGTH * -1) > retro.created_at) || retro.all_in?) retro.close retro.distribute_credits end end end desc "Start retrospectives that are ready to be started" task :start_retros => :environment do Project.all.each do |project| project.start_retro_if_ready end end ================================================ FILE: lib/tasks/cron.rake ================================================ task :cron => :environment do puts "Running cron..." Rake::Task['backup'].invoke Rake::Task['close_retros'].invoke Rake::Task['custom:refresh_active_members'].invoke Rake::Task['custom:lazy_majority'].invoke Rake::Task['custom:close_motions'].invoke Rake::Task['custom:refresh_activity_timelines'].invoke if Time.now.hour == 18 Rake::Task['custom:deliver_daily_digest'].invoke end if Time.now.hour == 0 Rake::Task['heroku:daily_backup'].invoke Rake::Task['start_retros'].invoke Rake::Task['custom:calculate_reputation'].invoke Rake::Task['custom:calculate_project_storage'].invoke Rake::Task['custom:detect_users_over_limit'].invoke Rake::Task['custom:detect_trial_expiration'].invoke Rake::Task['custom:refresh_project_issue_counts'].invoke end puts "done." end ================================================ FILE: lib/tasks/cucumber.rake ================================================ # This file was generated by $LOAD_PATH.unshift(RAILS_ROOT + '/vendor/plugins/cucumber/lib') if File.directory?(RAILS_ROOT + '/vendor/plugins/cucumber/lib') unless ARGV.any? {|a| a =~ /^gems/} begin require 'cucumber/rake/task' # Use vendored cucumber binary if possible. If it's not vendored, # Cucumber::Rake::Task will automatically use installed gem's cucumber binary vendored_cucumber_binary = Dir["#{RAILS_ROOT}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first namespace :cucumber do Cucumber::Rake::Task.new({:ok => 'custom:load_test_data'}, 'Run features that should pass') do |t| t.binary = vendored_cucumber_binary t.fork = true # You may get faster startup if you set this to false t.cucumber_opts = "--color --tags ~@wip --strict --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}" end Cucumber::Rake::Task.new({:wip => 'custom:load_test_data'}, 'Run features that are being worked on') do |t| t.binary = vendored_cucumber_binary t.fork = true # You may get faster startup if you set this to false t.cucumber_opts = "--color --tags @wip:2 --wip --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}" end desc 'Run all features' task :all => [:ok, :wip] end desc 'Alias for cucumber:ok' task :cucumber => 'cucumber:ok' task :default => :cucumber task :features => :cucumber do STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" end rescue LoadError desc 'cucumber rake task not available (cucumber not installed)' task :cucumber do abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' end end end ================================================ FILE: lib/tasks/custom.rake ================================================ namespace :custom do task :load_test_data => :environment do system 'rake db:test:prepare' system 'rake environment RAILS_ENV=test redmine:load_default_data' end task :refresh_active_members => :environment do Project.all.each do |project| puts "Refreshing #{project.id}: #{project.name}" project.refresh_active_members end end # Rejects/accepts lazy majority items that haven't had any activity for a certain number of days (LAZY_MAJORITY_NO_ACTIVITY_LENGTH) task :lazy_majority => :environment do Issue.all(:conditions => [ "status_id = ? AND updated_at < ?", IssueStatus.newstatus.id,DateTime.now - Setting::LAZY_MAJORITY_NO_ACTIVITY_LENGTH ]).each do |issue| issue.update_status end Issue.all(:conditions => [ "status_id = ? AND updated_at < ?", IssueStatus.estimate.id,DateTime.now - Setting::LAZY_MAJORITY_NO_ACTIVITY_LENGTH ]).each do |issue| issue.update_status end Issue.all(:conditions => [ "status_id = ? AND updated_at < ?", IssueStatus.done.id,DateTime.now - Setting::LAZY_MAJORITY_NO_ACTIVITY_LENGTH ]).each do |issue| issue.update_status end end #sanity checks all project issue counts task :refresh_project_issue_counts => :environment do Project.all.each do |p| p.refresh_issue_count end end # loops through active motions and makes a decision on them task :close_motions => :environment do Motion.allactive.each do |motion| motion.close end end task :calculate_reputation => :environment do Reputation.calculate_all end task :refresh_activity_timelines => :environment do Project.all_roots.each {|p| p.refresh_activity_line} end #one time fix for credits tables, and makes sure all projects have credit module enabled task :run_once_fix_credit_distros => :environment do CreditDistribution.all.each do |cd| credit = Credit.find(:first, :conditions => {:owner_id => cd.user_id, :amount => cd.amount}) credit.project_id = cd.project_id credit.save end Project.all.each do |p| p.enabled_modules << EnabledModule.create(:name => "credits") end end #maps all users to recurly users task :create_recurly_users => :environment do User.all.each do |user| User.create_recurly_account(user.id) end end task :calculate_project_storage => :environment do Project.all.each do |p| p.calculate_storage end end task :add_daily_digest_option_to_users => :environment do User.all.each do |user| user.pref.others.merge!({:daily_digest => true}) user.pref.save end end task :deliver_daily_digest => :environment do DailyDigest.deliver end task :deliver_personal_welcome => :environment do PersonalWelcome.deliver end #temporary task to re-assign admins that have been lost task :run_once_fix_owners => :environment do Project.all.each do |p| next unless p.administrators == [] && p.root? p.owner.add_to_project(p, Role.administrator) p.owner.add_to_project(p, Role.core_member) p.all_members.each do |ms| if ms.created_at > Time.now.advance(:minutes => -2) ms.created_at = p.created_at ms.save end ms.member_roles.each do |mr| if mr.created_at > Time.now.advance(:minutes => -2) mr.created_at = p.created_at mr.save end end end puts "fixed #{p.id} #{p.name}" end end #one time fix to add members again, after they've been lost task :run_once_rerun_membership_motions => :environment do Motion.find(:all, :conditions => ["created_at > ?", Time.parse('10/1/2010')], :order => "created_at ASC").each do |m| m.execute_action end end #fixing the dropped member roles by re-adding all accepted invitations task :run_once_fix_invitations => :environment do Invitation.all.each do |i| next if i.status == 0 unless i.user.enterprise_member_of?(i.project) puts "Adding #{i.user.name} #{i.user.id} to project #{i.project.name} #{i.project.id} as #{i.role.name}" i.user.add_to_project(i.project,i.role) end end end #Used to trim a production database for development task :trim_db => :environment do if ENV['reset_safe'] == 'true' puts "Trimming database" @p = Project.find(20) #bettermeans @p.name = "LOCAL BETTERMEANS" #changing title so there's not confusion when working with local db @p.save puts "Deleting private projects" Project.all.each do |p| next if p.is_public? puts "Deleting project #{p.id} #{p.name}" p.destroy puts "done." end puts "Changing passwords to 'password' " User.update_all(:hashed_password => "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8") puts "Changing emails" User.all.each do |user| user.mail = "#{(0...20).map{65.+(rand(25)).chr}.join}@bogus.com" user.save end puts "done." else puts "wont reset. we're not in development" puts "to allow reset use: export reset_safe=true" end end #detecting users that are overusing their plans and need to upgrade task :detect_users_over_limit => :environment do User.active.each do |u| u.update_usage_over end end #detecting users whose trials have expired and need to pay task :detect_trial_expiration => :environment do User.active.each do |u| u.update_trial_expiration end end #moving from features to tags task :run_once_features_to_task => :environment do Issue.all.each do |i| begin tag = i.tracker.name.downcase unless tag == "feature" puts("Upgrading issue #{i.id} #{tag}") i.update_attribute(:tag_list,i.tag_list.add(tag)) end i.update_attribute(:tags_copy, i.tags.join(",")) rescue puts("FAILED TO UPGRADE issue #{i.id}") end end end #moving from features to tags task :run_once_fix_retros => :environment do Retro.all.each do |r| if r.status_id == 3 && r.total_points > 0 dist = CreditDistribution.find_by_retro_id(r.id) unless dist puts("retro #{r.id} for #{r.project.name}") r.update_attribute(:status_id, 2) r.distribute_credits end end end end end ================================================ FILE: lib/tasks/deprecated.rake ================================================ def deprecated_task(name, new_name) task name=>new_name do $stderr.puts "\nNote: The rake task #{name} has been deprecated, please use the replacement version #{new_name}" end end deprecated_task :load_default_data, "redmine:load_default_data" deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis" deprecated_task :migrate_from_trac, "redmine:migrate_from_trac" ================================================ FILE: lib/tasks/email.rake ================================================ # Redmine - project management software # Copyright (C) 2006-2011 See readme for details and license # namespace :redmine do namespace :email do desc <<-END_DESC Read an email from standard input. General options: unknown_user=ACTION how to handle emails from an unknown user ACTION can be one of the following values: ignore: email is ignored (default) accept: accept as anonymous user create: create a user account no_permission_check=1 disable permission checking when receiving the email Issue attributes control options: project=PROJECT identifier of the target project status=STATUS name of the target status tracker=TRACKER name of the target tracker category=CATEGORY name of the target category priority=PRIORITY name of the target priority allow_override=ATTRS allow email content to override attributes specified by previous options ATTRS is a comma separated list of attributes Examples: # No project specified. Emails MUST contain the 'Project' keyword: rake redmine:email:read RAILS_ENV="production" < raw_email # Fixed project and default tracker specified, but emails can override # both tracker and priority attributes: rake redmine:email:read RAILS_ENV="production" \\ project=foo \\ tracker=bug \\ allow_override=tracker,priority < raw_email END_DESC task :read => :environment do options = { :issue => {} } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] MailHandler.receive(STDIN.read, options) end desc <<-END_DESC Read emails from an IMAP server. General options: unknown_user=ACTION how to handle emails from an unknown user ACTION can be one of the following values: ignore: email is ignored (default) accept: accept as anonymous user create: create a user account no_permission_check=1 disable permission checking when receiving the email Available IMAP options: host=HOST IMAP server host (default: 127.0.0.1) port=PORT IMAP server port (default: 143) ssl=SSL Use SSL? (default: false) username=USERNAME IMAP account password=PASSWORD IMAP password folder=FOLDER IMAP folder to read (default: INBOX) Issue attributes control options: project=PROJECT identifier of the target project status=STATUS name of the target status tracker=TRACKER name of the target tracker category=CATEGORY name of the target category priority=PRIORITY name of the target priority allow_override=ATTRS allow email content to override attributes specified by previous options ATTRS is a comma separated list of attributes Processed emails control options: move_on_success=MAILBOX move emails that were successfully received to MAILBOX instead of deleting them move_on_failure=MAILBOX move emails that were ignored to MAILBOX Examples: # No project specified. Emails MUST contain the 'Project' keyword: rake redmine:email:receive_iamp RAILS_ENV="production" \\ host=imap.foo.bar username=redmine@example.net password=xxx # Fixed project and default tracker specified, but emails can override # both tracker and priority attributes: rake redmine:email:receive_iamp RAILS_ENV="production" \\ host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ project=foo \\ tracker=bug \\ allow_override=tracker,priority END_DESC task :receive_imap => :environment do imap_options = {:host => ENV['host'], :port => ENV['port'], :ssl => ENV['ssl'], :username => ENV['username'], :password => ENV['password'], :folder => ENV['folder'], :move_on_success => ENV['move_on_success'], :move_on_failure => ENV['move_on_failure']} options = { :issue => {} } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] Redmine::IMAP.check(imap_options, options) end end end ================================================ FILE: lib/tasks/extract_fixtures.rake ================================================ desc 'Create YAML test fixtures from data in an existing database. Defaults to development database. Set RAILS_ENV to override.' task :extract_fixtures => :environment do sql = "SELECT * FROM %s" skip_tables = ["schema_info"] ActiveRecord::Base.establish_connection (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name| i = "000" File.open("#{RAILS_ROOT}/#{table_name}.yml", 'w' ) do |file| data = ActiveRecord::Base.connection.select_all(sql % table_name) file.write data.inject({}) { |hash, record| # cast extracted values ActiveRecord::Base.connection.columns(table_name).each { |col| record[col.name] = col.type_cast(record[col.name]) if record[col.name] } hash["#{table_name}_#{i.succ!}"] = record hash }.to_yaml end end end ================================================ FILE: lib/tasks/fetch_changesets.rake ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license # desc 'Fetch changesets from the repositories' namespace :redmine do task :fetch_changesets => :environment do Repository.fetch_changesets end end ================================================ FILE: lib/tasks/initializers.rake ================================================ desc 'Generates a configuration file for cookie store sessions.' file 'config/initializers/session_store.rb' do path = File.join(RAILS_ROOT, 'config', 'initializers', 'session_store.rb') secret = ActiveSupport::SecureRandom.hex(40) File.open(path, 'w') do |f| f.write <<"EOF" # This file was generated by 'rake config/initializers/session_store.rb', # and should not be made visible to public. # If you have a load-balancing Redmine cluster, you will need to use the # same version of this file on each machine. And be sure to restart your # server when you modify this file. # Your secret key for verifying cookie session data integrity. If you # change this key, all old sessions will become invalid! Make sure the # secret is at least 30 characters and all random, no regular words or # you'll be exposed to dictionary attacks. ActionController::Base.session = { :session_key => '_redmine_session', :secret => '#{secret}' } EOF end end ================================================ FILE: lib/tasks/load_default_data.rake ================================================ desc 'Load Redmine default configuration data. Language is chosen interactively or by setting REDMINE_LANG environment variable.' namespace :redmine do task :load_default_data => :environment do include Redmine::I18n envlang = 'en' if !envlang || !set_language_if_valid(envlang) puts while true print "Select language: " print valid_languages.collect(&:to_s).sort.join(", ") print " [#{current_language}] " STDOUT.flush lang = STDIN.gets.chomp! break if lang.empty? break if set_language_if_valid(lang) puts "Unknown language!" end STDOUT.flush puts "====================================" end begin Redmine::DefaultData::Loader.load(current_language) puts "Default configuration data loaded." rescue Redmine::DefaultData::DataAlreadyLoaded => error puts error rescue => error puts "Error: " + error puts "Default configuration data was not loaded." end end end ================================================ FILE: lib/tasks/locales.rake ================================================ namespace :locales do desc 'Updates language files based on en.yml content (only works for new top level keys).' task :update do dir = ENV['DIR'] || './config/locales' en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] files = Dir.glob(File.join(dir,'*.{yaml,yml}')) files.each do |file| puts "Updating file #{file}" file_strings = YAML.load(File.read(file)) file_strings = file_strings[file_strings.keys.first] missing_keys = en_strings.keys - file_strings.keys next if missing_keys.empty? puts "==> Missing #{missing_keys.size} keys (#{missing_keys.join(', ')})" lang = File.open(file, 'a') missing_keys.each do |key| {key => en_strings[key]}.to_yaml.each_line do |line| next if line =~ /^---/ || line.empty? puts " #{line}" lang << " #{line}" end end lang.close end end end ================================================ FILE: lib/tasks/metrics.rake ================================================ begin require 'metric_fu' rescue LoadError # Metric-fu not installed # http://metric-fu.rubyforge.org/ end ================================================ FILE: lib/tasks/migrate_from_mantis.rake ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# desc 'Mantis migration script' require 'active_record' require 'iconv' require 'pp' namespace :redmine do task :migrate_from_mantis => :environment do module MantisMigrate DEFAULT_STATUS = IssueStatus.default assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } STATUS_MAPPING = {10 => DEFAULT_STATUS, # new 20 => feedback_status, # feedback 30 => DEFAULT_STATUS, # acknowledged 40 => DEFAULT_STATUS, # confirmed 50 => assigned_status, # assigned 80 => resolved_status, # resolved 90 => closed_status # closed } priorities = Enumeration.priorities DEFAULT_PRIORITY = priorities[2] PRIORITY_MAPPING = {10 => priorities[1], # none 20 => priorities[1], # low 30 => priorities[2], # normal 40 => priorities[3], # high 50 => priorities[4], # urgent 60 => priorities[5] # immediate } TRACKER_BUG = Tracker.find_by_position(1) TRACKER_FEATURE = Tracker.find_by_position(2) roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer 25 => DEFAULT_ROLE, # reporter 40 => DEFAULT_ROLE, # updater 55 => developer_role, # developer 70 => manager_role, # manager 90 => manager_role # administrator } CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String 1 => 'int', # Numeric 2 => 'int', # Float 3 => 'list', # Enumeration 4 => 'string', # Email 5 => 'bool', # Checkbox 6 => 'list', # List 7 => 'list', # Multiselection list 8 => 'date', # Date } RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to 2 => IssueRelation::TYPE_RELATES, # parent of 3 => IssueRelation::TYPE_RELATES, # child of 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of 4 => IssueRelation::TYPE_DUPLICATES # has duplicate } class MantisUser < ActiveRecord::Base set_table_name :mantis_user_table def firstname @firstname = realname.blank? ? username : realname.split.first[0..29] @firstname.gsub!(/[^\w\s\'\-]/i, '') @firstname end def lastname @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29] @lastname.gsub!(/[^\w\s\'\-]/i, '') @lastname = '-' if @lastname.blank? @lastname end def email if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) && !User.find_by_mail(read_attribute(:email)) @email = read_attribute(:email) else @email = "#{username}@foo.bar" end end def username read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-') end end class MantisProject < ActiveRecord::Base set_table_name :mantis_project_table has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id has_many :news, :class_name => "MantisNews", :foreign_key => :project_id has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id def name read_attribute(:name)[0..29] end def identifier read_attribute(:name).underscore[0..19].gsub(/[^a-z0-9\-]/, '-') end end class MantisVersion < ActiveRecord::Base set_table_name :mantis_project_version_table def version read_attribute(:version)[0..29] end def description read_attribute(:description)[0..254] end end class MantisCategory < ActiveRecord::Base set_table_name :mantis_project_category_table end class MantisProjectUser < ActiveRecord::Base set_table_name :mantis_project_user_list_table end class MantisBug < ActiveRecord::Base set_table_name :mantis_bug_table belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id end class MantisBugText < ActiveRecord::Base set_table_name :mantis_bug_text_table # Adds Mantis steps_to_reproduce and additional_information fields # to description if any def full_description full_description = description full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank? full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank? full_description end end class MantisBugNote < ActiveRecord::Base set_table_name :mantis_bugnote_table belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id end class MantisBugNoteText < ActiveRecord::Base set_table_name :mantis_bugnote_text_table end class MantisBugFile < ActiveRecord::Base set_table_name :mantis_bug_file_table def size filesize end def original_filename MantisMigrate.encode(filename) end def content_type file_type end def read(*args) if @read_finished nil else @read_finished = true content end end end class MantisBugRelationship < ActiveRecord::Base set_table_name :mantis_bug_relationship_table end class MantisBugMonitor < ActiveRecord::Base set_table_name :mantis_bug_monitor_table end class MantisNews < ActiveRecord::Base set_table_name :mantis_news_table end class MantisCustomField < ActiveRecord::Base set_table_name :mantis_custom_field_table set_inheritance_column :none has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id def format read_attribute :type end def name read_attribute(:name)[0..29].gsub(/[^\w\s\'\-]/, '-') end end class MantisCustomFieldProject < ActiveRecord::Base set_table_name :mantis_custom_field_project_table end class MantisCustomFieldString < ActiveRecord::Base set_table_name :mantis_custom_field_string_table end def self.migrate # Users print "Migrating users" User.delete_all "login <> 'admin'" users_map = {} users_migrated = 0 MantisUser.find(:all).each do |user| u = User.new :firstname => encode(user.firstname), :lastname => encode(user.lastname), :mail => user.email, :last_login_on => user.last_visit u.login = user.username u.password = 'mantis' u.status = User::STATUS_LOCKED if user.enabled != 1 u.admin = true if user.access_level == 90 next unless u.save! users_migrated += 1 users_map[user.id] = u.id print '.' end puts # Projects print "Migrating projects" Project.destroy_all projects_map = {} versions_map = {} categories_map = {} MantisProject.find(:all).each do |project| p = Project.new :name => encode(project.name), :description => encode(project.description) p.identifier = project.identifier next unless p.save projects_map[project.id] = p.id p.enabled_module_names = ['issue_tracking', 'news', 'wiki'] p.trackers << TRACKER_BUG p.trackers << TRACKER_FEATURE print '.' # Project members project.members.each do |member| m = Member.new :user => User.find_by_id(users_map[member.user_id]), :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE] m.project = p m.save end # Project versions project.versions.each do |version| v = Version.new :name => encode(version.version), :description => encode(version.description), :effective_date => version.date_order.to_date v.project = p v.save versions_map[version.id] = v.id end # Project categories project.categories.each do |category| g = IssueCategory.new :name => category.category[0,30] g.project = p g.save categories_map[category.category] = g.id end end puts # Bugs print "Migrating bugs" Issue.destroy_all issues_map = {} keep_bug_ids = (Issue.count == 0) MantisBug.find_each(:batch_size => 200) do |bug| next unless projects_map[bug.project_id] && users_map[bug.reporter_id] i = Issue.new :project_id => projects_map[bug.project_id], :subject => encode(bug.summary), :description => encode(bug.bug_text.full_description), :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY, :created_at => bug.date_submitted, :updated_at => bug.last_updated i.author = User.find_by_id(users_map[bug.reporter_id]) i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank? i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank? i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG) i.id = bug.id if keep_bug_ids next unless i.save issues_map[bug.id] = i.id print '.' # Assignee # Redmine checks that the assignee is a project member if (bug.handler_id && users_map[bug.handler_id]) i.assigned_to = User.find_by_id(users_map[bug.handler_id]) i.save_with_validation(false) end # Bug notes bug.bug_notes.each do |note| next unless users_map[note.reporter_id] n = Journal.new :notes => encode(note.bug_note_text.note), :created_at => note.date_submitted n.user = User.find_by_id(users_map[note.reporter_id]) n.journalized = i n.save end # Bug files bug.bug_files.each do |file| a = Attachment.new :created_at => file.date_added a.file = file a.author = User.find :first a.container = i a.save end # Bug monitors bug.bug_monitors.each do |monitor| next unless users_map[monitor.user_id] i.add_watcher(User.find_by_id(users_map[monitor.user_id])) end end # update issue id sequence if needed (postgresql) Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') puts # Bug relationships print "Migrating bug relations" MantisBugRelationship.find(:all).each do |relation| next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id]) pp r unless r.save print '.' end puts # News print "Migrating news" News.destroy_all MantisNews.find(:all, :conditions => 'project_id > 0').each do |news| next unless projects_map[news.project_id] n = News.new :project_id => projects_map[news.project_id], :title => encode(news.headline[0..59]), :description => encode(news.body), :created_at => news.date_posted n.author = User.find_by_id(users_map[news.poster_id]) n.save print '.' end puts # Custom fields print "Migrating custom fields" IssueCustomField.destroy_all MantisCustomField.find(:all).each do |field| f = IssueCustomField.new :name => field.name[0..29], :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], :min_length => field.length_min, :max_length => field.length_max, :regexp => field.valid_regexp, :possible_values => field.possible_values.split('|'), :is_required => field.require_report? next unless f.save print '.' # Trackers association f.trackers = Tracker.find :all # Projects association field.projects.each do |project| f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id] end # Values field.values.each do |value| v = CustomValue.new :custom_field_id => f.id, :value => value.value v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id] v.save end unless f.new_record? end puts puts puts "Users: #{users_migrated}/#{MantisUser.count}" puts "Projects: #{Project.count}/#{MantisProject.count}" puts "Memberships: #{Member.count}/#{MantisProjectUser.count}" puts "Versions: #{Version.count}/#{MantisVersion.count}" puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}" puts "Bugs: #{Issue.count}/#{MantisBug.count}" puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}" puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}" puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}" puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}" puts "News: #{News.count}/#{MantisNews.count}" puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}" end def self.encoding(charset) @ic = Iconv.new('UTF-8', charset) rescue Iconv::InvalidEncoding return false end def self.establish_connection(params) constants.each do |const| klass = const_get(const) next unless klass.respond_to? 'establish_connection' klass.establish_connection params end end def self.encode(text) @ic.iconv text rescue text end end puts if Redmine::DefaultData::Loader.no_data? puts "Redmine configuration need to be loaded before importing data." puts "Please, run this first:" puts puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" exit end puts "WARNING: Your Redmine data will be deleted during this process." print "Are you sure you want to continue ? [y/N] " break unless STDIN.gets.match(/^y$/i) # Default Mantis database settings db_params = {:adapter => 'mysql', :database => 'bugtracker', :host => 'localhost', :username => 'root', :password => '' } puts puts "Please enter settings for your Mantis database" [:adapter, :host, :database, :username, :password].each do |param| print "#{param} [#{db_params[param]}]: " value = STDIN.gets.chomp! db_params[param] = value unless value.blank? end while true print "encoding [UTF-8]: " encoding = STDIN.gets.chomp! encoding = 'UTF-8' if encoding.blank? break if MantisMigrate.encoding encoding puts "Invalid encoding!" end puts # Make sure bugs can refer bugs in other projects Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' # Turn off email notifications Setting.notified_events = [] MantisMigrate.establish_connection db_params MantisMigrate.migrate end end ================================================ FILE: lib/tasks/migrate_from_trac.rake ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2006-2011 See readme for details and license# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'active_record' require 'iconv' require 'pp' namespace :redmine do desc 'Trac migration script' task :migrate_from_trac => :environment do module TracMigrate TICKET_MAP = [] DEFAULT_STATUS = IssueStatus.default assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } STATUS_MAPPING = {'new' => DEFAULT_STATUS, 'reopened' => feedback_status, 'assigned' => assigned_status, 'closed' => closed_status } priorities = Enumeration.priorities DEFAULT_PRIORITY = priorities[0] PRIORITY_MAPPING = {'lowest' => priorities[0], 'low' => priorities[0], 'normal' => priorities[1], 'high' => priorities[2], 'highest' => priorities[3], # --- 'trivial' => priorities[0], 'minor' => priorities[1], 'major' => priorities[2], 'critical' => priorities[3], 'blocker' => priorities[4] } TRACKER_BUG = Tracker.find_by_position(1) TRACKER_FEATURE = Tracker.find_by_position(2) DEFAULT_TRACKER = TRACKER_BUG TRACKER_MAPPING = {'defect' => TRACKER_BUG, 'enhancement' => TRACKER_FEATURE, 'task' => TRACKER_FEATURE, 'patch' =>TRACKER_FEATURE } roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last ROLE_MAPPING = {'admin' => manager_role, 'developer' => developer_role } class ::Time class << self alias :real_now :now def now real_now - @fake_diff.to_i end def fake(time) @fake_diff = real_now - time res = yield @fake_diff = 0 res end end end class TracComponent < ActiveRecord::Base set_table_name :component end class TracMilestone < ActiveRecord::Base set_table_name :milestone # If this attribute is set a milestone has a defined target timepoint def due if read_attribute(:due) && read_attribute(:due) > 0 Time.at(read_attribute(:due)).to_date else nil end end # This is the real timepoint at which the milestone has finished. def completed if read_attribute(:completed) && read_attribute(:completed) > 0 Time.at(read_attribute(:completed)).to_date else nil end end def description # Attribute is named descr in Trac v0.8.x has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) end end class TracTicketCustom < ActiveRecord::Base set_table_name :ticket_custom end class TracAttachment < ActiveRecord::Base set_table_name :attachment set_inheritance_column :none def time; Time.at(read_attribute(:time)) end def original_filename filename end def content_type Redmine::MimeType.of(filename) || '' end def exist? File.file? trac_fullpath end def open File.open("#{trac_fullpath}", 'rb') {|f| @file = f yield self } end def read(*args) @file.read(*args) end def description read_attribute(:description).to_s.slice(0,255) end private def trac_fullpath attachment_type = read_attribute(:type) trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) } "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" end end class TracTicket < ActiveRecord::Base set_table_name :ticket set_inheritance_column :none # ticket changes: only migrate status changes and comments has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket has_many :attachments, :class_name => "TracAttachment", :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" + ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket def ticket_type read_attribute(:type) end def summary read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary) end def description read_attribute(:description).blank? ? summary : read_attribute(:description) end def time; Time.at(read_attribute(:time)) end def changetime; Time.at(read_attribute(:changetime)) end end class TracTicketChange < ActiveRecord::Base set_table_name :ticket_change def time; Time.at(read_attribute(:time)) end end TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \ TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \ TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \ TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \ TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ CamelCase TitleIndex) class TracWikiPage < ActiveRecord::Base set_table_name :wiki set_primary_key :name has_many :attachments, :class_name => "TracAttachment", :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" + " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" + ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\'' def self.columns # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) super.select {|column| column.name.to_s != 'readonly'} end def time; Time.at(read_attribute(:time)) end end class TracPermission < ActiveRecord::Base set_table_name :permission end class TracSessionAttribute < ActiveRecord::Base set_table_name :session_attribute end def self.find_or_create_user(username, project_member = false) return User.anonymous if username.blank? u = User.find_by_login(username) if !u # Create a new user if not found mail = username[0,limit_for(User, 'mail')] if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') mail = mail_attr.value end mail = "#{mail}@foo.bar" unless mail.include?("@") name = username if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') name = name_attr.value end name =~ (/(.*)(\s+\w+)?/) fn = $1.strip ln = ($2 || '-').strip u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'), :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-') u.login = username[0,limit_for(User, 'login')].gsub(/[^a-z0-9_\-@\.]/i, '-') u.password = 'trac' u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') # finally, a default user is used if the new user is not valid u = User.find(:first) unless u.save end # Make sure he is a member of the project if project_member && !u.member_of?(@target_project) role = DEFAULT_ROLE if u.admin role = ROLE_MAPPING['admin'] elsif TracPermission.find_by_username_and_action(username, 'developer') role = ROLE_MAPPING['developer'] end Member.create(:user => u, :project => @target_project, :roles => [role]) u.reload end u end # Basic wiki syntax conversion def self.convert_wiki_text(text) # Titles text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"} # External Links text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"} # Ticket links: # [ticket:234 Text],[ticket:234 This is a test] text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1') # ticket:1234 # #1 is working cause Redmine uses the same syntax. text = text.gsub(/ticket\:([^\ ]+)/, '#\1') # Milestone links: # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)] # The text "Milestone 0.1.0 (Mercury)" is not converted, # cause Redmine's wiki does not support this. text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"') # [milestone:"0.1.0 Mercury"] text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"') text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"') # milestone:0.1.0 text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1') text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1') # Internal Links text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"} # Links to pages UsingJustWikiCaps text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') # Normalize things that were supposed to not be links # like !NotALink text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2') # Revisions links text = text.gsub(/\[(\d+)\]/, 'r\1') # Ticket number re-writing text = text.gsub(/#(\d+)/) do |s| if $1.length < 10 TICKET_MAP[$1.to_i] ||= $1 "\##{TICKET_MAP[$1.to_i] || $1}" else s end end # We would like to convert the Code highlighting too # This will go into the next line. shebang_line = false # Reguar expression for start of code pre_re = /\{\{\{/ # Code hightlighing... shebang_re = /^\#\!([a-z]+)/ # Regular expression for end of code pre_end_re = /\}\}\}/ # Go through the whole text..extract it line by line text = text.gsub(/^(.*)$/) do |line| m_pre = pre_re.match(line) if m_pre line = '
    '
              else
                m_sl = shebang_re.match(line)
                if m_sl
                  shebang_line = true
                  line = ''
                end
                m_pre_end = pre_end_re.match(line)
                if m_pre_end
                  line = '
    ' if shebang_line line = '' + line end end end line end # Highlighting text = text.gsub(/'''''([^\s])/, '_*\1') text = text.gsub(/([^\s])'''''/, '\1*_') text = text.gsub(/'''/, '*') text = text.gsub(/''/, '_') text = text.gsub(/__/, '+') text = text.gsub(/~~/, '-') text = text.gsub(/`/, '@') text = text.gsub(/,,/, '~') # Lists text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "} text end def self.migrate establish_connection # Quick database test TracComponent.count migrated_components = 0 migrated_milestones = 0 migrated_tickets = 0 migrated_custom_values = 0 migrated_ticket_attachments = 0 migrated_wiki_edits = 0 migrated_wiki_attachments = 0 #Wiki system initializing... @target_project.wiki.destroy if @target_project.wiki @target_project.reload wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart') wiki_edit_count = 0 # Components print "Migrating components" issues_category_map = {} TracComponent.find(:all).each do |component| print '.' STDOUT.flush c = IssueCategory.new :project => @target_project, :name => encode(component.name[0, limit_for(IssueCategory, 'name')]) next unless c.save issues_category_map[component.name] = c migrated_components += 1 end puts # Milestones print "Migrating milestones" version_map = {} TracMilestone.find(:all).each do |milestone| print '.' STDOUT.flush # First we try to find the wiki page... p = wiki.find_or_new_page(milestone.name.to_s) p.content = WikiContent.new(:page => p) if p.new_record? p.content.text = milestone.description.to_s p.content.author = find_or_create_user('trac') p.content.comments = 'Milestone' p.save v = Version.new :project => @target_project, :name => encode(milestone.name[0, limit_for(Version, 'name')]), :description => nil, :wiki_page_title => milestone.name.to_s, :effective_date => milestone.completed next unless v.save version_map[milestone.name] = v migrated_milestones += 1 end puts # Custom fields # TODO: read trac.ini instead print "Migrating custom fields" custom_field_map = {} TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field| print '.' STDOUT.flush # Redmine custom field name field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize # Find if the custom already exists in Redmine f = IssueCustomField.find_by_name(field_name) # Or create a new one f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, :field_format => 'string') next if f.new_record? f.trackers = Tracker.find(:all) f.projects << @target_project custom_field_map[field.name] = f end puts # Trac 'resolution' field as a Redmine custom field r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) r = IssueCustomField.new(:name => 'Resolution', :field_format => 'list', :is_filter => true) if r.nil? r.trackers = Tracker.find(:all) r.projects << @target_project r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.save! custom_field_map['resolution'] = r # Tickets print "Migrating tickets" TracTicket.find_each(:batch_size => 200) do |ticket| print '.' STDOUT.flush i = Issue.new :project => @target_project, :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]), :description => convert_wiki_text(encode(ticket.description)), :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY, :created_at => ticket.time i.author = find_or_create_user(ticket.reporter) i.category = issues_category_map[ticket.component] unless ticket.component.blank? i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER i.id = ticket.id unless Issue.exists?(ticket.id) next unless Time.fake(ticket.changetime) { i.save } TICKET_MAP[ticket.id] = i.id migrated_tickets += 1 # Owner unless ticket.owner.blank? i.assigned_to = find_or_create_user(ticket.owner, true) Time.fake(ticket.changetime) { i.save } end # Comments and status/resolution changes ticket.changes.group_by(&:time).each do |time, changeset| status_change = changeset.select {|change| change.field == 'status'}.first resolution_change = changeset.select {|change| change.field == 'resolution'}.first comment_change = changeset.select {|change| change.field == 'comment'}.first n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''), :created_at => time n.user = find_or_create_user(changeset.first.author) n.journalized = i if status_change && STATUS_MAPPING[status_change.oldvalue] && STATUS_MAPPING[status_change.newvalue] && (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) n.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :old_value => STATUS_MAPPING[status_change.oldvalue].id, :value => STATUS_MAPPING[status_change.newvalue].id) end if resolution_change n.details << JournalDetail.new(:property => 'cf', :prop_key => custom_field_map['resolution'].id, :old_value => resolution_change.oldvalue, :value => resolution_change.newvalue) end n.save unless n.details.empty? && n.notes.blank? end # Attachments ticket.attachments.each do |attachment| next unless attachment.exist? attachment.open { a = Attachment.new :created_at => attachment.time a.file = attachment a.author = find_or_create_user(attachment.author) a.container = i a.description = attachment.description migrated_ticket_attachments += 1 if a.save } end # Custom fields custom_values = ticket.customs.inject({}) do |h, custom| if custom_field = custom_field_map[custom.name] h[custom_field.id] = custom.value migrated_custom_values += 1 end h end if custom_field_map['resolution'] && !ticket.resolution.blank? custom_values[custom_field_map['resolution'].id] = ticket.resolution end i.custom_field_values = custom_values i.save_custom_field_values end # update issue id sequence if needed (postgresql) Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!') puts # Wiki print "Migrating wiki" if wiki.save TracWikiPage.find(:all, :order => 'name, version').each do |page| # Do not migrate Trac manual wiki pages next if TRAC_WIKI_PAGES.include?(page.name) wiki_edit_count += 1 print '.' STDOUT.flush p = wiki.find_or_new_page(page.name) p.content = WikiContent.new(:page => p) if p.new_record? p.content.text = page.text p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac' p.content.comments = page.comment Time.fake(page.time) { p.new_record? ? p.save : p.content.save } next if p.content.new_record? migrated_wiki_edits += 1 # Attachments page.attachments.each do |attachment| next unless attachment.exist? next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page attachment.open { a = Attachment.new :created_at => attachment.time a.file = attachment a.author = find_or_create_user(attachment.author) a.description = attachment.description a.container = p migrated_wiki_attachments += 1 if a.save } end end wiki.reload wiki.pages.each do |page| page.content.text = convert_wiki_text(page.content.text) Time.fake(page.content.updated_at) { page.content.save } end end puts puts puts "Components: #{migrated_components}/#{TracComponent.count}" puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}" puts "Tickets: #{migrated_tickets}/#{TracTicket.count}" puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}" puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}" puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s end def self.limit_for(klass, attribute) klass.columns_hash[attribute.to_s].limit end def self.encoding(charset) @ic = Iconv.new('UTF-8', charset) rescue Iconv::InvalidEncoding puts "Invalid encoding!" return false end def self.set_trac_directory(path) @@trac_directory = path raise "This directory doesn't exist!" unless File.directory?(path) raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory) @@trac_directory rescue Exception => e puts e return false end def self.trac_directory @@trac_directory end def self.set_trac_adapter(adapter) return false if adapter.blank? raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter) # If adapter is sqlite or sqlite3, make sure that trac.db exists raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path) @@trac_adapter = adapter rescue Exception => e puts e return false end def self.set_trac_db_host(host) return nil if host.blank? @@trac_db_host = host end def self.set_trac_db_port(port) return nil if port.to_i == 0 @@trac_db_port = port.to_i end def self.set_trac_db_name(name) return nil if name.blank? @@trac_db_name = name end def self.set_trac_db_username(username) @@trac_db_username = username end def self.set_trac_db_password(password) @@trac_db_password = password end def self.set_trac_db_schema(schema) @@trac_db_schema = schema end mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password def self.trac_db_path; "#{trac_directory}/db/trac.db" end def self.trac_attachments_directory; "#{trac_directory}/attachments" end def self.target_project_identifier(identifier) project = Project.find_by_identifier(identifier) if !project # create the target project project = Project.new :name => identifier.humanize, :description => '' project.identifier = identifier puts "Unable to create a project with identifier '#{identifier}'!" unless project.save # enable issues and wiki for the created project project.enabled_module_names = ['issue_tracking', 'wiki'] else puts puts "This project already exists in your Redmine database." print "Are you sure you want to append data to this project ? [Y/n] " exit if STDIN.gets.match(/^n$/i) end project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) @target_project = project.new_record? ? nil : project end def self.connection_params if %w(sqlite sqlite3).include?(trac_adapter) {:adapter => trac_adapter, :database => trac_db_path} else {:adapter => trac_adapter, :database => trac_db_name, :host => trac_db_host, :port => trac_db_port, :username => trac_db_username, :password => trac_db_password, :schema_search_path => trac_db_schema } end end def self.establish_connection constants.each do |const| klass = const_get(const) next unless klass.respond_to? 'establish_connection' klass.establish_connection connection_params end end private def self.encode(text) @ic.iconv text rescue text end end puts if Redmine::DefaultData::Loader.no_data? puts "Redmine configuration need to be loaded before importing data." puts "Please, run this first:" puts puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" exit end puts "WARNING: a new project will be added to Redmine during this process." print "Are you sure you want to continue ? [y/N] " break unless STDIN.gets.match(/^y$/i) puts def prompt(text, options = {}, &block) default = options[:default] || '' while true print "#{text} [#{default}]: " value = STDIN.gets.chomp! value = default if value.blank? break if yield value end end DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432} prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip} prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter} unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter) prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host} prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port} prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name} prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema} prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username} prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password} end prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} puts # Turn off email notifications Setting.notified_events = [] TracMigrate.migrate end end ================================================ FILE: lib/tasks/migrate_plugins.rake ================================================ namespace :db do desc 'Migrates installed plugins.' task :migrate_plugins => :environment do if Rails.respond_to?('plugins') Rails.plugins.each do |plugin| next unless plugin.respond_to?('migrate') puts "Migrating #{plugin.name}..." plugin.migrate end else puts "Undefined method plugins for Rails!" puts "Make sure engines plugin is installed." end end end ================================================ FILE: lib/tasks/plugins.rake ================================================ require 'source_annotation_extractor' # Modified version of the SourceAnnotationExtractor in railties # Will search for runable code that uses call_hook class PluginSourceAnnotationExtractor < SourceAnnotationExtractor # Returns a hash that maps filenames under +dir+ (recursively) to arrays # with their annotations. Only files with annotations are included, and only # those with extension +.builder+, +.rb+, +.rxml+, +.rjs+, +.rhtml+, and +.erb+ # are taken into account. def find_in(dir) results = {} Dir.glob("#{dir}/*") do |item| next if File.basename(item)[0] == ?. if File.directory?(item) results.update(find_in(item)) elsif item =~ /(hook|test)\.rb/ # skip elsif item =~ /\.(builder|(r(?:b|xml|js)))$/ results.update(extract_annotations_from(item, /\s*(#{tag})\(?\s*(.*)$/)) elsif item =~ /\.(rhtml|erb)$/ results.update(extract_annotations_from(item, /<%=\s*\s*(#{tag})\(?\s*(.*?)\s*%>/)) end end results end end namespace :redmine do namespace :plugins do desc "Enumerate all Redmine plugin hooks and their context parameters" task :hook_list do PluginSourceAnnotationExtractor.enumerate 'call_hook' end end end ================================================ FILE: lib/tasks/reek.rake ================================================ namespace :reek do task :statements do dirs_to_reek = ['app/models', 'app/controllers', 'app/helpers'] files_to_reek = dirs_to_reek.map { |dir| Dir[File.join(dir, "**/*.rb")] } output = `reek #{files_to_reek.join(' ')}` long_methods = output.lines.select { |line| line =~ /TooManyStatements/ } long_methods.sort! do |line_1, line_2| line_1.split[3].to_i <=> line_2.split[3].to_i end puts long_methods end end ================================================ FILE: lib/tasks/reminder.rake ================================================ # BetterMeans - Work 2.0 # Copyright (C) 2008 Shereef Bishay # desc <<-END_DESC Send reminders about issues due in the next days. Available options: * days => number of days to remind about (defaults to 7) * tracker => id of tracker (defaults to all trackers) * project => id or identifier of project (defaults to all projects) Example: rake redmine:send_reminders days=7 RAILS_ENV="production" END_DESC namespace :redmine do task :send_reminders => :environment do options = {} options[:days] = ENV['days'].to_i if ENV['days'] options[:project] = ENV['project'] if ENV['project'] options[:tracker] = ENV['tracker'].to_i if ENV['tracker'] Mailer.reminders(options) end end ================================================ FILE: lib/tasks/remove_problem_type.rake ================================================ # Used once desc "removes item of type : problem and changes them to type : taks" task :remove_problem_type => :environment do problem = Tracker.first(:conditions => {:name => "Problem"}) task = Tracker.first(:conditions => {:name => "Task"}) Issue.find(:all, :conditions => {:tracker_id => problem.id }).each do |issue| issue.tracker = task issue.save puts "#{issue.subject}" end end ================================================ FILE: lib/tasks/reset_all_passwords.rake ================================================ task :reset_all_passwords => :environment do if ENV['reset_safe'] == 'true' puts "Resetting all passwords to 'password'..." User.update_all(:hashed_password => "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8") puts "done." else puts "wont reset. we're not in development" puts "to allow reset use: export reset_safe=true" end end ================================================ FILE: lib/tasks/rspec.rake ================================================ gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9 rspec_gem_dir = nil Dir["#{RAILS_ROOT}/vendor/gems/*"].each do |subdir| rspec_gem_dir = subdir if subdir.gsub("#{RAILS_ROOT}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb") end rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec') if rspec_gem_dir && (test ?d, rspec_plugin_dir) raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n" end if rspec_gem_dir $LOAD_PATH.unshift("#{rspec_gem_dir}/lib") elsif File.exist?(rspec_plugin_dir) $LOAD_PATH.unshift("#{rspec_plugin_dir}/lib") end # Don't load rspec if running "rake gems:*" unless ARGV.any? {|a| a =~ /^gems/} begin require 'spec/rake/spectask' rescue MissingSourceFile module Spec module Rake class SpecTask def initialize(name) task name do # if rspec-rails is a configured gem, this will output helpful material and exit ... require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment")) # ... otherwise, do this: raise <<-MSG #{"*" * 80} * You are trying to run an rspec rake task defined in * #{__FILE__}, * but rspec can not be found in vendor/gems, vendor/plugins or system gems. #{"*" * 80} MSG end end end end end end Rake.application.instance_variable_get('@tasks').delete('default') spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop task :noop do end task :default => :spec task :stats => "spec:statsetup" desc "Run all specs in spec directory (excluding plugin specs)" Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList['spec/**/*_spec.rb'] end namespace :spec do desc "Run all specs in spec directory with RCov (excluding plugin specs)" Spec::Rake::SpecTask.new(:rcov) do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList['spec/**/*_spec.rb'] t.rcov = true t.rcov_opts = lambda do IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten end end desc "Print Specdoc for all specs (excluding plugin specs)" Spec::Rake::SpecTask.new(:doc) do |t| t.spec_opts = ["--format", "specdoc", "--dry-run"] t.spec_files = FileList['spec/**/*_spec.rb'] end desc "Print Specdoc for all plugin examples" Spec::Rake::SpecTask.new(:plugin_doc) do |t| t.spec_opts = ["--format", "specdoc", "--dry-run"] t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*') end [:models, :controllers, :views, :helpers, :lib, :integration].each do |sub| desc "Run the code examples in spec/#{sub}" Spec::Rake::SpecTask.new(sub => spec_prereq) do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"] end end desc "Run the code examples in vendor/plugins (except RSpec's own)" Spec::Rake::SpecTask.new(:plugins => spec_prereq) do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList['vendor/plugins/**/spec/**/*_spec.rb'].exclude('vendor/plugins/rspec/*').exclude("vendor/plugins/rspec-rails/*") end namespace :plugins do desc "Runs the examples for rspec_on_rails" Spec::Rake::SpecTask.new(:rspec_on_rails) do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList['vendor/plugins/rspec-rails/spec/**/*_spec.rb'] end end # Setup specs for stats task :statsetup do require 'code_statistics' ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models') ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views') ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers') ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers') ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib') ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing') ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration') ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models') ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views') ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers') ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers') ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib') ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing') ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration') end namespace :db do namespace :fixtures do desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z." task :load => :environment do ActiveRecord::Base.establish_connection(Rails.env) base_dir = File.join(Rails.root, 'spec', 'fixtures') fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir require 'active_record/fixtures' (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) end end end end end end ================================================ FILE: lib/tasks/steak.rake ================================================ unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks begin require 'spec/rake/spectask' rescue MissingSourceFile module Spec module Rake class SpecTask def initialize(name) task name do # if rspec-rails is a configured gem, this will output helpful material and exit ... require File.expand_path(File.join(File.dirname(__FILE__),"..","..","config","environment")) # ... otherwise, do this: raise <<-MSG #{"*" * 80} * You are trying to run an rspec rake task defined in * #{__FILE__}, * but rspec can not be found in vendor/gems, vendor/plugins or system gems. #{"*" * 80} MSG end end end end end end namespace :spec do desc "Run the code examples in spec/acceptance" Spec::Rake::SpecTask.new(:acceptance => "db:test:prepare") do |t| t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""] t.spec_files = FileList["spec/acceptance/**/*_spec.rb"] end # Setup stats to include acceptance specs task :statsetup do require 'code_statistics' ::STATS_DIRECTORIES << %w(Acceptance\ specs spec/acceptance) if File.exist?('spec/acceptance') ::CodeStatistics::TEST_TYPES << "Acceptance specs" if File.exist?('spec/acceptance') end end end ================================================ FILE: lib/tasks/testing.rake ================================================ ### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake namespace :test do desc 'Measures test coverage' task :coverage do rm_f "coverage" rm_f "coverage.data" rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib --html" files = Dir.glob("test/**/*_test.rb").join(" ") system("#{rcov} #{files}") system("open coverage/index.html") if PLATFORM['darwin'] end namespace :scm do namespace :setup do desc "Creates directory for test repositories" task :create_dir do FileUtils.mkdir_p Rails.root + '/tmp/test' end supported_scms = [:subversion, :cvs, :bazaar, :mercurial, :git, :darcs, :filesystem] desc "Creates a test subversion repository" task :subversion => :create_dir do repo_path = "tmp/test/subversion_repository" system "svnadmin create #{repo_path}" system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" end (supported_scms - [:subversion]).each do |scm| desc "Creates a test #{scm} repository" task scm => :create_dir do system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" end end desc "Creates all test repositories" task :all => supported_scms end end end ================================================ FILE: lib/tasks/update_item_statuses.rake ================================================ # Updates item status to Done or Canceled depending on what their % complete is # Used once for migrating to new more defined item statuses desc "update item statuses to Done or Canceled depending on what their % complete is" task :update_item_statuses => :environment do done = IssueStatus.first(:conditions => {:name => "Done"}) canceled = IssueStatus.first(:conditions => {:name => "Canceled"}) closed = IssueStatus.first(:conditions => {:name => "Closed"}) Issue.find(:all, :conditions => {:status_id => closed.id }).each do |issue| issue.done_ratio < 100 ? issue.status = canceled : issue.status = done issue.save end end ================================================ FILE: lib/tasks/watchers.rake ================================================ desc 'Removes watchers from what they can no longer view.' namespace :redmine do namespace :watchers do task :prune => :environment do Watcher.prune end end end ================================================ FILE: public/.htaccess ================================================ # General Apache options AddHandler fastcgi-script .fcgi AddHandler fcgid-script .fcgi AddHandler cgi-script .cgi Options +FollowSymLinks +ExecCGI # If you don't want Rails to look in certain directories, # use the following rewrite rules so that Apache won't rewrite certain requests # # Example: # RewriteCond %{REQUEST_URI} ^/notrails.* # RewriteRule .* - [L] # Redirect all requests not available on the filesystem to Rails # By default the cgi dispatcher is used which is very slow # # For better performance replace the dispatcher with the fastcgi one # # Example: # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On # If your Rails application is accessed via an Alias directive, # then you MUST also set the RewriteBase in this htaccess file. # # Example: # Alias /myrailsapp /path/to/myrailsapp/public # RewriteBase /myrailsapp RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # In case Rails experiences terminal errors # Instead of displaying this message you can supply a file here which will be rendered instead # # Example: # ErrorDocument 500 /500.html ErrorDocument 500 "

    Application error

    Rails application failed to start properly" ================================================ FILE: public/404.html ================================================ BetterMeans 404 error

    Page not found

    The page you were trying to access doesn't exist or has been removed.

    Back

    ================================================ FILE: public/500.html ================================================ BetterMeans 500 error

    Oops. Something went wrong: Internal error

    We've dispatched our geeks, and they're on it. Really sorry about that!

    Back

    ================================================ FILE: public/help/wiki_syntax.html ================================================ Wiki formatting

    Wiki Syntax Quick Reference

    Font Styles
    Strong*Strong*Strong
    Italic_Italic_Italic
    Underline+Underline+Underline
    Deleted-Deleted-Deleted
    ??Quote??Quote
    Inline Code@Inline Code@Inline Code
    Preformatted text<pre>
     lines
     of code
    </pre>
     lines
     of code
    
    Lists
    Unordered list* Item 1
    * Item 2
    • Item 1
    • Item 2
    Ordered list# Item 1
    # Item 2
    1. Item 1
    2. Item 2
    Headings
    Heading 1h1. Title 1

    Title 1

    Heading 2h2. Title 2

    Title 2

    Heading 3h3. Title 3

    Title 3

    Links
    http://foo.barhttp://foo.bar
    "Foo":http://foo.barFoo
    BetterMeans links
    Link to a Wiki page[[Wiki page]]Wiki page
    Issue #12Issue #12
    Revision r43Revision r43
    commit:f30e13e43f30e13e4
    source:some/filesource:some/file
    Inline images
    Image!image_url!
    !attached_image!

    More Information

    ================================================ FILE: public/help/wiki_syntax_detailed.html ================================================ BetterMeansWikiFormatting

    Wiki formatting

    Links

    BetterMeans links

    BetterMeans allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.

    • Link to an issue: #124 (displays #124, link is striked-through if the issue is closed)
    • Link to a changeset: r758 (displays r758)
    • Link to a changeset with a non-numeric hash: commit:c6f4d0fd (displays c6f4d0fd). Added in r1236.

    Wiki links:

    • [[Guide]] displays a link to the page named 'Guide': Guide
    • [[Guide#further-reading]] takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: Guide
    • [[Guide|User manual]] displays a link to the same page but with a different text: User manual

    You can also link to pages of an other project wiki:

    • [[sandbox:some page]] displays a link to the page named 'Some page' of the Sandbox wiki
    • [[sandbox:]] displays a link to the Sandbox wiki main page

    Wiki links are displayed in red if the page doesn't exist yet, eg: Nonexistent page.

    Links to others resources (0.7):

    • Documents:
      • document#17 (link to document with id 17)
      • document:Greetings (link to the document with title "Greetings")
      • document:"Some document" (double quotes can be used when document title contains spaces)
    • Versions:
      • version#3 (link to version with id 3)
      • version:1.0.0 (link to version named "1.0.0")
      • version:"1.0 beta 2"
    • Attachments:
      • attachment:file.zip (link to the attachment of the current object named file.zip)
      • For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)
    • Repository files
      • source:some/file -- Link to the file located at /some/file in the project's repository
      • source:some/file@52 -- Link to the file's revision 52
      • source:some/file#L120 -- Link to line 120 of the file
      • source:some/file@52#L120 -- Link to line 120 of the file's revision 52
      • export:some/file -- Force the download of the file

    Escaping (0.7):

    • You can prevent BetterMeans links from being parsed by preceding them with an exclamation mark: !

    External links

    HTTP URLs and email addresses are automatically turned into clickable links:

    http://www.BetterMeans.org, someone@foo.bar
    

    displays: http://www.BetterMeans.org,

    If you want to display a specific text instead of the URL, you can use the standard textile syntax:

    "BetterMeans web site":http://www.BetterMeans.org
    

    displays: BetterMeans web site

    Text formatting

    For things such as headlines, bold, tables, lists, BetterMeans supports Textile syntax. See http://www.textism.com/tools/textile/ for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.

    Font style

    * *bold*
    * _italic_
    * _*bold italic*_
    * +underline+
    * -strike-through-
    

    Display:

    • bold
    • italic
    • *bold italic*
    • underline
    • strike-through

    Inline images

    • !image_url! displays an image located at image_url (textile syntax)
    • !>image_url! right floating image
    • If you have an image attached to your wiki page, it can be displayed inline using its filename: !attached_image.png!

    Headings

    h1. Heading
    h2. Subheading
    h3. Subsubheading
    

    BetterMeans assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.

    Paragraphs

    p>. right aligned
    p=. centered
    

    This is centered paragraph.

    Blockquotes

    Start the paragraph with bq.

    bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.
    

    Display:

    Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
    To go live, all you need to add is a database and a web server.

    Table of content

    {{toc}} => left aligned toc
    {{>toc}} => right aligned toc
    

    Macros

    BetterMeans has the following builtin macros:

    hello_world

    Sample macro.

    include

    Include a wiki page. Example:

    {{include(Foo)}}
    macro_list

    Displays a list of all available macros, including description if available.

    Code highlighting

    Code highlightment relies on CodeRay, a fast syntax highlighting library written completely in Ruby. It currently supports c, html, javascript, rhtml, ruby, scheme, xml languages.

    You can highlight code in your wiki page using this syntax:

    <pre><code class="ruby">
      Place you code here.
    </code></pre>
    

    Example:

     1 # The Greeter class
     2 class Greeter
     3   def initialize(name)
     4     @name = name.capitalize
     5   end
     6
     7   def salute
     8     puts "Hello #{@name}!"
     9   end
    10 end
    
    
    ================================================ FILE: public/javascripts/application.js ================================================ /* BetterMeans - Work 2.0 Copyright (C) 2006-2011 See readme for details and license */ var jumpbox_text = ""; var community_members = {}; //used by @mention autocomplete function initialize(){ arm_fancybox(); prep_jumpbox(); break_long_words(); bind_autocomplete_mentions(); } function break_long_words(){ $('.long-words').breakWords(); } function arm_fancybox(){ if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))) { return; } $("a.fancyframe").fancybox({ 'speedIn' : 0, 'speedOut' : 0, 'overlayShow' : true, 'width' : '90%', 'height' : '95%', 'autoScale' : false, 'moddal' : false, 'hideOnOverlayClick' : true, 'transitionIn' : 'none', 'transitionOut' : 'none', 'type' : 'iframe' }).click(function(){ $.fancybox.showActivity(); $('#fancybox-frame').load(function(){ $.fancybox.hideActivity(); $("#fancybox-frame").contents().find("a[href*=/]").not("a[target*=top]").attr('target', '_blank'); }); }); } function arm_checkboxes(){ $(".cb-enable").click(function(){ var parent = $(this).parents('.switch'); $('.cb-disable',parent).removeClass('selected'); $(this).addClass('selected'); $('.checkbox',parent).attr('checked', true); }); $(".cb-disable").click(function(){ var parent = $(this).parents('.switch'); $('.cb-enable',parent).removeClass('selected'); $(this).addClass('selected'); $('.checkbox',parent).attr('checked', false); }); } function prep_jumpbox(){ jumpbox_text = $('#jumpbox :selected').text(); $('#jumpbox :selected').text($.trim($('#jumpbox :selected').text())); adjust_jumpbox_width(); $('#jumpbox').focus(function(){ $('#jumpbox :selected').text(jumpbox_text); //adjust_jumpbox_width(); $('#jumpbox').width('auto'); }); $('#jumpbox').focusout(function(){ $('#jumpbox :selected').text($.trim($('#jumpbox :selected').text())); $('#jumpbox').css('background','#323232'); adjust_jumpbox_width(); }); $('#jumpbox').change(function(){ $('#jumpbox :selected').text($.trim($('#jumpbox :selected').text())); $('#jumpbox').css('background','#323232'); adjust_jumpbox_width(); }); } function adjust_jumpbox_width(){ jumpbox_width = $('#widthcalc').html($('#jumpbox :selected').text()).width(); if (jumpbox_width > 10){ $('#jumpbox').width(jumpbox_width + 35); } } function show_fancybox(url,message){ $.fancybox({ 'width' : '90%', 'height' : '95%', 'autoScale' : false, 'transitionIn' : 'none', 'transitionOut' : 'none', 'speedIn' : '0', 'speedOut' : '0', 'type' : 'iframe', 'href' : url }); $('#fancybox-frame').load(function(){ $('#fancy-loading').hide(); $("#fancybox-frame").contents().find("a[href*=/]").not("a[target*=top]").attr('target', '_blank'); }); $('#fancybox-inner').prepend("
    " + message + "
    "); } function checkAll (id, checked) { $("form#" + id + " INPUT[type='checkbox']").attr('checked', checked); } function toggleCheckboxesBySelector(selector) { boxes = $$(selector); var all_checked = true; for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; } } function showAndScrollTo(id, focus) { $('#' + id).show(); if (focus!=null) { $('#' + focus).focus(); } $('#' + focus).parent().scrollTo('#' + focus); } function toggleRowGroup(el) { var tr = Element.up(el, 'tr'); var n = Element.next(tr); tr.toggleClassName('open'); while (n != undefined && !n.hasClassName('group')) { Element.toggle(n); n = Element.next(n); } } function toggleFieldset(el) { var fieldset = Element.up(el, 'fieldset'); fieldset.toggleClassName('collapsed'); Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2}); } var fileFieldCount = 1; function addFileField() { if (fileFieldCount >= 10) return false; fileFieldCount++; var f = document.createElement("input"); f.type = "file"; f.name = "attachments[" + fileFieldCount + "][file]"; f.size = 30; var d = document.createElement("input"); d.type = "text"; d.name = "attachments[" + fileFieldCount + "][description]"; d.size = 60; p = document.getElementById("attachments_fields"); p.appendChild(document.createElement("br")); p.appendChild(f); p.appendChild(d); } function showTab(name) { $('.tab-content').hide(); $('.tab-top').removeClass("selected"); $('#tab-content-' + name).show(); $('#tab-' + name).addClass("selected"); return false; } function moveTabRight(el) { var lis = Element.up(el, 'div.tabs').down('ul').childElements(); var tabsWidth = 0; var i; for (i=0; i0) { lis[i-1].show(); } } function displayTabsButtons() { var lis; var tabsWidth = 0; var i; $$('div.tabs').each(function(el) { lis = el.down('ul').childElements(); for (i=0; i 0) { // Element.show('ajax-indicator'); // } // }, // onComplete: function(){ // if ($('ajax-indicator') && Ajax.activeRequestCount == 0) { // Element.hide('ajax-indicator'); // } // } // }); $(document).ajaxStart(function() { $('#ajax-indicator').show(); }); $(document).ajaxStop(function() { $('#ajax-indicator').hide(); }); $(document).ajaxSend(function(event, request, settings) { if (typeof(AUTH_TOKEN) == "undefined") return; // settings.data is a serialized string like "foo=bar&baz=boink" (or null) if (settings.type == 'GET') return; // Don't add anything to a get request let IE turn it into a POST. settings.data = settings.data || ""; settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN); }); ///HELPERS function url_for(options){ // THINKABOUTTHIS: Is it worth using Rails' routes for this instead? var url = '/' + options['controller'] ; if(options['id']!=null) url += "/" + options['id']; if(options['action']!=null && options['action'].match(/index/)==null) url += '/' + options['action']; // var keys = Object.keys(options).select(function(key){ return (key!="controller" && key!="action" && key!="id"); }); // if(keys.length>0) url += "?"; // // keys.each(function(key, index){ // url += key + "=" + options[key]; // if(index and " characters // with their equivalent html counter parts // function h(s) { var escaped = s; escaped = escaped.replace(/\r\n/g, "xxxxxx11"); escaped = escaped.replace(/\n/g, "xxxxxx11"); escaped = escaped.replace(/
    /g, "xxxxxx11"); escaped = escaped.replace(/&/g, "&"); escaped = escaped.replace(//g, ">"); escaped = escaped.replace(/\"/g, """); escaped = escaped.replace(/xxxxxx11/g, "\r\n"); return escaped; } function text_only(text){ text = "

    " + text + "

    "; text = $(text).text(); return text; } function display_sparks(){ $('.spark').each(function(){ $(this).show(); if(!$(this).attr('max')){ return; } var max = parseFloat($(this).attr('max')); if (max > 15){ max = 15; } if (max == 0){ max = 1; } $(this).sparkline('html', {type: 'bar' , barColor: 'grey', chartRangeMax: max, height: 15}); if ($(this).is(":visible")){ $(this).removeAttr("max"); //so we don't sparkline it again } // $(this).removeClass("spark"); // if (isNaN(max)){ // $(this).sparkline('html', {type: 'bar' , barColor: 'grey'}); // } // else{ // $(this).sparkline('html', {type: 'bar' , barColor: 'grey', height: max}); // } }); } //hides right column, and expands left one if right column is empty // function hide_empty_right_column(){ // if ($('.gt-right-col').html().length < 100){ // $('.gt-right-col').hide(); // $('.gt-left-col').width('100%'); // } // } function humane_date(date_str){ var time_formats = [ [60, 'Just Now'], [90, '1 Minute'], // 60*1.5 [3600, 'Minutes', 60], // 60*60, 60 [5400, '1 Hour'], // 60*60*1.5 [86400, 'Hours', 3600], // 60*60*24, 60*60 [129600, '1 Day'], // 60*60*24*1.5 [604800, 'Days', 86400], // 60*60*24*7, 60*60*24 [907200, '1 Week'], // 60*60*24*7*1.5 [2628000, 'Weeks', 604800], // 60*60*24*(365/12), 60*60*24*7 [3942000, '1 Month'], // 60*60*24*(365/12)*1.5 [31536000, 'Months', 2628000], // 60*60*24*365, 60*60*24*(365/12) [47304000, '1 Year'], // 60*60*24*365*1.5 [3153600000, 'Years', 31536000], // 60*60*24*365*100, 60*60*24*365 [4730400000, '1 Century'] // 60*60*24*365*100*1.5 ]; var dt = new Date, seconds = ((dt - new Date(date_str)) / 1000), token = ' Ago', prepend = '', i = 0, format; if (seconds < 0) { seconds = Math.abs(seconds); token = ''; prepend = 'In '; } while (format = time_formats[i++]) { if (seconds < format[0]) { if (format.length == 2) { return (i>1?prepend:'') + format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago } else { return prepend + Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : ''); } } } // overflow for centuries if(seconds > 4730400000) return Math.round(seconds / 4730400000) + ' Centuries' + token; return date_str; }; function promptToRemote(text, param, url) { value = prompt(text + ':'); if (value) { new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true}); return false; } } //param must start with & function send_remote(url,param,note){ top.send_remote(url,param,note) // top.$.ajax({ // type: "POST", // dataType: "json", // url: url, // data: '¬e=' + note + param, // timeout: 30000 //30 seconds // }); } function comment_prompt_to_remote(dataId,title,message,param,url,required){ var content = ''; var note = "$('#prompt_comment_" + dataId + "').val()" ; content = content + '

    ' + title + '


    '; if (message){ content = content + message + '

    '; } content = content + '


    '; content = content + '

    '; content = content + ''; if (!required){ content = content + ''; } content = content + ''; content = content + '



    '; $.fancybox( { 'content' : content, 'width' : 'auto', 'height' : 'auto', 'title' : title, 'autoScale' : false, 'transitionIn' : 'none', 'transitionOut' : 'none', 'scrolling' : 'no', 'showCloseButton' : false, 'modal' : true, 'href' : '#comment_prompt' }); $('#prompt_comment_' + dataId).focus(); } $.fn.mybubbletip = function(tip, options) { var _this = $(this); var _options = { positionAt: 'element', // element | body | mouse positionAtElement: _this, offsetTop: 0, offsetLeft: 0, deltaPosition: 0, deltaDirection: 'up', // direction: up | down | left | right // animationDuration: 250, // animationEasing: 'swing', // linear | swing bindShow: 'mouseover', // mouseover | focus | click | etc. bindHide: 'mouseout', // mouseout | blur | etc. delayShow: 0, delayHide: 500 }; if (options) { _options = $.extend(_options, options); } $(this).bind(_options.bindShow,function() { $(this).bubbletip(tip,_options); }); }; (function($) { $.fn.getGravatar = function(options) { //debug(this); // build main options before element iteration var opts = $.extend({}, $.fn.getGravatar.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; var t = ""; //check to see if we're working with an text input first if($this.is("input[type='text']")){ //do an initial check of the value $.fn.getGravatar.getUrl(o, $this.val()); //do our ajax call for the MD5 hash every time a key is released $this.keyup(function(){ clearTimeout(t); var email = $this.val(); t = setTimeout(function(){$.fn.getGravatar.getUrl(o, email);}, 500); }); } }); }; // // define and expose our functions // $.fn.getGravatar.getUrl = function(o, email){ //call the start function if in use if(o.start) o.start($this); //call MD5 function id = email; // id = $.fn.getGravatar.md5(email); var gravatar_url = "https://secure.gravatar.com/avatar.php?gravatar_id="+id+"&size="+o.avatarSize; //call our function to output the avatar to the container $.fn.getGravatar.output(o.avatarContainer, gravatar_url, o.stop); } $.fn.getGravatar.output = function(avatarContainer, gravatar_url, stop) { //replace the src of our avatar container with the gravatar url $(avatarContainer).attr("src", gravatar_url); $(avatarContainer).show(); if(stop) stop(); }; $.fn.getGravatar.md5 = function(str) { // Calculate the md5 hash of a string // // version: 909.322 // discuss at: http://phpjs.org/functions/md5 // + original by: Webtoolkit.info (http://www.webtoolkit.info/) // + namespaced by: Michael White (http://getsprink.com) // + tweaked by: Jack // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Brett Zamir (http://brett-zamir.me) // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // - depends on: utf8_encode // * example 1: md5('Kevin van Zonneveld'); // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9' var xl; var rotateLeft = function (lValue, iShiftBits) { return (lValue<>>(32-iShiftBits)); }; var addUnsigned = function (lX,lY) { var lX4,lY4,lX8,lY8,lResult; lX8 = (lX & 0x80000000); lY8 = (lY & 0x80000000); lX4 = (lX & 0x40000000); lY4 = (lY & 0x40000000); lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); if (lX4 & lY4) { return (lResult ^ 0x80000000 ^ lX8 ^ lY8); } if (lX4 | lY4) { if (lResult & 0x40000000) { return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); } else { return (lResult ^ 0x40000000 ^ lX8 ^ lY8); } } else { return (lResult ^ lX8 ^ lY8); } }; var _F = function (x,y,z) { return (x & y) | ((~x) & z); }; var _G = function (x,y,z) { return (x & z) | (y & (~z)); }; var _H = function (x,y,z) { return (x ^ y ^ z); }; var _I = function (x,y,z) { return (y ^ (x | (~z))); }; var _FF = function (a,b,c,d,x,s,ac) { a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac)); return addUnsigned(rotateLeft(a, s), b); }; var _GG = function (a,b,c,d,x,s,ac) { a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac)); return addUnsigned(rotateLeft(a, s), b); }; var _HH = function (a,b,c,d,x,s,ac) { a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac)); return addUnsigned(rotateLeft(a, s), b); }; var _II = function (a,b,c,d,x,s,ac) { a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac)); return addUnsigned(rotateLeft(a, s), b); }; var convertToWordArray = function (str) { var lWordCount; var lMessageLength = str.length; var lNumberOfWords_temp1=lMessageLength + 8; var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; var lNumberOfWords = (lNumberOfWords_temp2+1)*16; var lWordArray=new Array(lNumberOfWords-1); var lBytePosition = 0; var lByteCount = 0; while ( lByteCount < lMessageLength ) { lWordCount = (lByteCount-(lByteCount % 4))/4; lBytePosition = (lByteCount % 4)*8; lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount)<>>29; return lWordArray; }; var wordToHex = function (lValue) { var wordToHexValue="",wordToHexValue_temp="",lByte,lCount; for (lCount = 0;lCount<=3;lCount++) { lByte = (lValue>>>(lCount*8)) & 255; wordToHexValue_temp = "0" + lByte.toString(16); wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length-2,2); } return wordToHexValue; }; var x=[], k,AA,BB,CC,DD,a,b,c,d, S11=7, S12=12, S13=17, S14=22, S21=5, S22=9 , S23=14, S24=20, S31=4, S32=11, S33=16, S34=23, S41=6, S42=10, S43=15, S44=21; str = $.fn.getGravatar.utf8_encode(str); x = convertToWordArray(str); a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; xl = x.length; for (k=0;k 127 && c1 < 2048) { enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); } else { enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); } if (enc !== null) { if (end > start) { utftext += string.substring(start, end); } utftext += enc; start = end = n+1; } } if (end > start) { utftext += string.substring(start, string.length); } return utftext; } // // plugin defaults // $.fn.getGravatar.defaults = { fallback: '', avatarSize: 50, avatarContainer: '#gravatar', start: null, stop: null }; })(jQuery); /* * bubbletip * * Copyright (c) 2009, UhLeeKa * Version: * 1.0.4 * Licensed under the GPL license: * http://www.gnu.org/licenses/gpl.html * Author Website: * http://www.uhleeka.com * Description: * A bubble-styled tooltip extension * - multiple tips on a page * - multiple tips per jQuery element * - tips open outward in four directions: * - up * - down * - left * - right * - tips can be: * - anchored to the triggering jQuery element * - absolutely positioned * - opened at the current mouse coordinates * - anchored to a specified jQuery element * - IE png transparency is handled via filters */ var bindIndex = 0; var mouse_over_bubble = false; $.fn.extend({ bubbletip: function(tip, options) { // check to see if the tip is a descendant of // a table.bubbletip element and therefore // has already been instantiated as a bubbletip if ($('table.bubbletip #' + $(tip).id).length > 0) { return this; } var _this, _tip, _calc, _timeoutAnimate, _timeoutRefresh, _isActive, _isHiding, _wrapper, _bindIndex; _this = $(this); _tip = $(tip); _bindIndex = bindIndex++; // for window.resize namespace binding var _options = { positionAt: 'element', // element | body | mouse positionAtElement: _this, offsetTop: 0, offsetLeft: 0, deltaPosition: 0, deltaDirection: 'up', // direction: up | down | left | right animationDuration: 0, // animationEasing: 'swing', // linear | swing bindShow: 'mouseover', // mouseover | focus | click | etc. bindHide: 'mouseout', // mouseout | blur | etc. delayShow: 0, delayHide: 500 }; if (options) { _options = $.extend(_options, options); } // calculated values _calc = { top: 0, left: 0, delta: 0, mouseTop: 0, mouseLeft: 0, tipHeight: 0, bindShow: (_options.bindShow + ' ').replace(/ +/g, '.bubbletip' + _bindIndex), bindHide: (_options.bindHide + ' ').replace(/ +/g, '.bubbletip' + _bindIndex) }; _timeoutAnimate = null; _timeoutRefresh = null; _isActive = false; _isHiding = false; // store the tip id for removeBubbletip if (!_this.data('bubbletip_tips')) { _this.data('bubbletip_tips', [[_tip.get(0).id, _calc.bindShow, _calc.bindHide, _bindIndex]]); } else { _this.data('bubbletip_tips', $.merge(_this.data('bubbletip_tips'), [[_tip.get(0).id, _calc.bindShow, _calc.bindHide, _bindIndex]])); } // validate _options if (!_options.positionAt.match(/^element|body|mouse$/i)) { _options.positionAt = 'element'; } if (!_options.deltaDirection.match(/^up|down|left|right$/i)) { _options.deltaDirection = 'up'; } // create the wrapper table element create_wrapper(false); _Calculate(true); show_tip(); // return false; $('.bubbletip').bind('mouseover',function(){ mouse_over_bubble = true; }); $('.bubbletip').bind('mouseout', function() { mouse_over_bubble = false; //BUGBUG: change to false }); $([_wrapper.get(0), this.get(0)]).bind(_calc.bindHide, function() { if (_timeoutAnimate) { clearTimeout(_timeoutAnimate); } _timeoutAnimate = setTimeout(function() { if (!mouse_over_bubble) { _HideWrapper(); // _tip.appendTo('body'); // $('.bubbletip').remove(); //removeBubbletip(tip); } }, _options.delayHide); return false; }); function show_tip(){ if (_timeoutAnimate) { clearTimeout(_timeoutAnimate); } _timeoutAnimate = setTimeout(function() { if (_isActive) { return; } _isActive = true; if (_isHiding) { _wrapper.stop(true, false); } var animation; if (_options.positionAt.match(/^element|body$/i)) { if (_options.deltaDirection.match(/^up|down$/i)) { if (!_isHiding) { _wrapper.css('top', parseInt(_calc.top + _calc.delta,10) + 'px'); } animation = { 'opacity': 1, 'top': _calc.top + 'px' }; } else { if (!_isHiding) { _wrapper.css('left', parseInt(_calc.left + _calc.delta,10) + 'px'); } animation = { 'opacity': 1, 'left': _calc.left + 'px' }; } } else { if (_options.deltaDirection.match(/^up|down$/i)) { if (!_isHiding) { _calc.mouseTop = e.pageY + _calc.top; _wrapper.css({ 'top': parseInt(_calc.mouseTop + _calc.delta,10) + 'px', 'left': parseInt(e.pageX - (_wrapper.width() / 2),10) + 'px' }); } animation = { 'opacity': 1, 'top': _calc.mouseTop + 'px' }; } else { if (!_isHiding) { _calc.mouseLeft = e.pageX + _calc.left; _wrapper.css({ 'left': parseInt(_calc.mouseLeft + _calc.delta,10) + 'px', 'top': parseInt(e.pageY - (_wrapper.height() / 2),10) + 'px' }); } animation = { 'opacity': 1, 'left': _calc.left + 'px' }; } } _isHiding = false; _wrapper.show(); _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() { _wrapper.css('opacity', ''); _isActive = true; // $('.bubbletip').remove(); }); }, _options.delayShow); } function create_wrapper(noTip){ if (noTip) { _wrapper = $('
    '); } else { if (_options.deltaDirection.match(/^up$/i)) { _wrapper = $('
    '); } else if (_options.deltaDirection.match(/^down$/i)) { _wrapper = $('
    '); } else if (_options.deltaDirection.match(/^left$/i)) { _wrapper = $('
    '); } else if (_options.deltaDirection.match(/^right$/i)) { _wrapper = $('
    '); } } // append the wrapper to the document body _wrapper.appendTo('body'); _wrapper.width(_tip.width() + 66); // apply IE filters to _wrapper elements if ((/msie/.test(navigator.userAgent.toLowerCase())) && (!/opera/.test(navigator.userAgent.toLowerCase()))) { $('*', _wrapper).each(function() { var image = $(this).css('background-image'); if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) { image = RegExp.$1; $(this).css({ 'backgroundImage': 'none', 'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=' + ($(this).css('backgroundRepeat') == 'no-repeat' ? 'crop' : 'scale') + ', src=\'' + image + '\')' }).each(function() { var position = $(this).css('position'); if (position != 'absolute' && position != 'relative') $(this).css('position', 'relative'); }); } }); } // move the tip element into the content section of the wrapper $('.bt-content', _wrapper).append(_tip); // show the tip (in case it is hidden) so that we can calculate its dimensions _tip.show(); // handle left|right delta if (_options.deltaDirection.match(/^left|right$/i)) { // tail is 40px, so divide height by two and subtract 20px; _calc.tipHeight = parseInt(_tip.height() / 2,10); // handle odd integer height if ((_tip.height() % 2) == 1) { _calc.tipHeight++; } _calc.tipHeight = (_calc.tipHeight < 20) ? 1 : _calc.tipHeight - 20; if (_options.deltaDirection.match(/^left$/i)) { $('div.bt-right', _wrapper).css('height', _calc.tipHeight + 'px'); } else { $('div.bt-left', _wrapper).css('height', _calc.tipHeight + 'px'); } } // set the opacity of the wrapper to 0 _wrapper.css('opacity', 0); // execute initial calculations } function _HideWrapper() { var animation; _isActive = false; _isHiding = true; if (_options.positionAt.match(/^element|body$/i)) { if (_options.deltaDirection.match(/^up|down$/i)) { animation = { 'opacity': 0, 'top': parseInt(_calc.top - _calc.delta,10) + 'px' }; } else { animation = { 'opacity': 0, 'left': parseInt(_calc.left - _calc.delta,10) + 'px' }; } } else { if (_options.deltaDirection.match(/^up|down$/i)) { animation = { 'opacity': 0, 'top': parseInt(_calc.mouseTop - _calc.delta,10) + 'px' }; } else { animation = { 'opacity': 0, 'left': parseInt(_calc.mouseLeft - _calc.delta,10) + 'px' }; } } _wrapper.animate(animation, _options.animationDuration, _options.animationEasing, function() { _wrapper.hide(); _isHiding = false; _tip.appendTo('body'); _tip.hide(); _wrapper.hide(); _wrapper.addClass('oldbubble'); $('.oldbubble').hide(); }); }; function _Calculate(firstTime) { // calculate values if (_options.positionAt.match(/^element$/i)) { var offset = _options.positionAtElement.offset(); if (_options.deltaDirection.match(/^up$/i)) { _calc.top = offset.top + _options.offsetTop - _wrapper.height(); _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2); _calc.delta = _options.deltaPosition; } else if (_options.deltaDirection.match(/^down$/i)) { _calc.top = offset.top + _options.positionAtElement.height() + _options.offsetTop; _calc.left = offset.left + _options.offsetLeft + ((_options.positionAtElement.width() - _wrapper.width()) / 2); _calc.delta = -_options.deltaPosition; } else if (_options.deltaDirection.match(/^left$/i)) { _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2); _calc.left = offset.left + _options.offsetLeft - _wrapper.width(); _calc.delta = _options.deltaPosition; } else if (_options.deltaDirection.match(/^right$/i)) { _calc.top = offset.top + _options.offsetTop + ((_options.positionAtElement.height() - _wrapper.height()) / 2); _calc.left = offset.left + _options.positionAtElement.width() + _options.offsetLeft; _calc.delta = -_options.deltaPosition; } } else if (_options.positionAt.match(/^body$/i)) { if (_options.deltaDirection.match(/^up|left$/i)) { _calc.top = _options.offsetTop; _calc.left = _options.offsetLeft; // up or left _calc.delta = _options.deltaPosition; } else { if (_options.deltaDirection.match(/^down$/i)) { _calc.top = parseInt(_options.offsetTop + _wrapper.height(),10); _calc.left = _options.offsetLeft; } else { _calc.top = _options.offsetTop; _calc.left = parseInt(_options.offsetLeft + _wrapper.width(),10); } // down or right _calc.delta = -_options.deltaPosition; } } else if (_options.positionAt.match(/^mouse$/i)) { if (_options.deltaDirection.match(/^up|left$/i)) { if (_options.deltaDirection.match(/^up$/i)) { _calc.top = -(_options.offsetTop + _wrapper.height()); _calc.left = _options.offsetLeft; } else if (_options.deltaDirection.match(/^left$/i)) { _calc.top = _options.offsetTop; _calc.left = -(_options.offsetLeft + _wrapper.width()); } // up or left _calc.delta = _options.deltaPosition; } else { _calc.top = _options.offsetTop; _calc.left = _options.offsetLeft; // down or right _calc.delta = -_options.deltaPosition; } } //Flip //first handle corners // //bottom right // if (((_calc.left + _wrapper.width()) > $(window).width())&&((_calc.top + _wrapper.height()) > $(window).height())){ // create_wrapper(true); // _calc.top = $(window).height() - _wrapper.height(); // _calc.left = $(window).width() - _wrapper.width(); // } // // //bottom left // if ((_calc.left < 0)&&((_calc.top + _wrapper.height()) > $(window).height())){ // create_wrapper(true); // _calc.top = $(window).height() - _wrapper.height(); // _calc.left = 0; // } // // //top right // if (((_calc.left + _wrapper.width()) > $(window).width())&&((_calc.top < 0))){ // create_wrapper(true); // _calc.top = 0; // _calc.left = $(window).width() - _wrapper.width(); // } // // //top left // if ((_calc.left < 0)&&(_calc.top < 0 )){ // create_wrapper(true); // _calc.top = 0; // _calc.left = 0; // } if (_calc.top < 0){ _options.deltaDirection = "down"; if (firstTime) { create_wrapper(false); _Calculate(false); return false; } } if (_calc.left < 0){ _options.deltaDirection = "right"; if (firstTime) { create_wrapper(false); _Calculate(false); return false; } } if ((_calc.left + _wrapper.width()) > $(window).width()){ _options.deltaDirection = "left"; if (firstTime) { create_wrapper(false); _Calculate(false); return false; } } if ((_calc.top + _wrapper.height()) > $(window).height()){ _options.deltaDirection = "up"; if (firstTime) { create_wrapper(false); _Calculate(false); return false; } } //Nudge edges if ((_calc.left + _wrapper.width()) > $(window).width()){ create_wrapper(true); _calc.left = $(window).width() - _wrapper.width(); } // if ((_calc.top + _wrapper.height()) > $(window).height()){ // create_wrapper(true); // _calc.top = $(window).height() - _wrapper.height(); // } if (_calc.left < 0){ create_wrapper(true); _calc.left = 0; } if (_calc.top < 0){ create_wrapper(true); _calc.top = 0; } // hide _wrapper.hide(); _wrapper.addClass('oldbubble'); $('.oldbubble').hide(); // handle the wrapper (element|body) positioning if (_options.positionAt.match(/^element|body$/i)) { _wrapper.css({ 'position': 'absolute', 'top': _calc.top + 'px', 'left': _calc.left + 'px' }); } return true; }; return this; }// , // removeBubbletip: function(tips) { // $('.bubbletip').remove(); // var tipsActive; // var tipsToRemove = new Array(); // var arr, i, ix; // var elem; // // tipsActive = $.makeArray($(this).data('bubbletip_tips')); // // // convert the parameter array of tip id's or elements to id's // arr = $.makeArray(tips); // for (i = 0; i < arr.length; i++) { // tipsToRemove.push($(arr[i]).get(0).id); // } // // for (i = 0; i < tipsActive.length; i++) { // ix = null; // if ((tipsToRemove.length == 0) || ((ix = $.inArray(tipsActive[i][0], tipsToRemove)) >= 0)) { // // remove all tips if there are none specified // // otherwise, remove only specified tips // // // find the surrounding table.bubbletip // elem = $('#' + tipsActive[i][0]).get(0).parentNode; // while (elem.tagName.toLowerCase() != 'table') { // elem = elem.parentNode; // } // // attach the tip element to body and hide // $(tipsActive[i][0]).appendTo('body').hide(); // // remove the surrounding table.bubbletip // $(elem).remove(); // // // unbind show/hide events // $(this).unbind(tipsActive[i][1]).unbind([i][2]); // // // unbind window.resize event // $(window).unbind('resize.bubbletip' + tipsActive[i][3]); // } // } // // return this; // } }); /* * Date Format 1.2.3 * (c) 2007-2009 Steven Levithan * MIT license * * Includes enhancements by Scott Trenda * and Kris Kowal * * Accepts a date, a mask, or a date and a mask. * Returns a formatted version of the given date. * The date defaults to the current date/time. * The mask defaults to dateFormat.masks.default. */ var dateFormat = function () { var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, timezoneClip = /[^-+\dA-Z]/g, pad = function (val, len) { val = String(val); len = len || 2; while (val.length < len) val = "0" + val; return val; }; // Regexes and supporting functions are cached through closure return function (date, mask, utc) { var dF = dateFormat; // You can't provide utc if you skip other args (use the "UTC:" mask prefix) if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { mask = date; date = undefined; } // Passing date through Date applies Date.parse, if necessary date = date ? new Date(date) : new Date; if (isNaN(date)) throw SyntaxError("invalid date"); mask = String(dF.masks[mask] || mask || dF.masks["default"]); // Allow setting the utc argument via the mask if (mask.slice(0, 4) == "UTC:") { mask = mask.slice(4); utc = true; } var _ = utc ? "getUTC" : "get", d = date[_ + "Date"](), D = date[_ + "Day"](), m = date[_ + "Month"](), y = date[_ + "FullYear"](), H = date[_ + "Hours"](), M = date[_ + "Minutes"](), s = date[_ + "Seconds"](), L = date[_ + "Milliseconds"](), o = utc ? 0 : date.getTimezoneOffset(), flags = { d: d, dd: pad(d), ddd: dF.i18n.dayNames[D], dddd: dF.i18n.dayNames[D + 7], m: m + 1, mm: pad(m + 1), mmm: dF.i18n.monthNames[m], mmmm: dF.i18n.monthNames[m + 12], yy: String(y).slice(2), yyyy: y, h: H % 12 || 12, hh: pad(H % 12 || 12), H: H, HH: pad(H), M: M, MM: pad(M), s: s, ss: pad(s), l: pad(L, 3), L: pad(L > 99 ? Math.round(L / 10) : L), t: H < 12 ? "a" : "p", tt: H < 12 ? "am" : "pm", T: H < 12 ? "A" : "P", TT: H < 12 ? "AM" : "PM", Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] }; return mask.replace(token, function ($0) { return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); }); }; }(); // Some common format strings dateFormat.masks = { "default": "ddd mmm dd yyyy HH:MM:ss", shortDate: "m/d/yy", mediumDate: "mmm d, yyyy", longDate: "mmmm d, yyyy", fullDate: "dddd, mmmm d, yyyy", shortTime: "h:MM TT", mediumTime: "h:MM:ss TT", longTime: "h:MM:ss TT Z", isoDate: "yyyy-mm-dd", isoTime: "HH:MM:ss", isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" }; // Internationalization strings dateFormat.i18n = { dayNames: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], monthNames: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] }; jQuery.fn.texthighlight = function(pat) { function innerHighlight(node, pat) { var skip = 0; if (node.nodeType == 3) { var pos = node.data.toUpperCase().indexOf(pat); if (pos >= 0) { var spannode = document.createElement('span'); spannode.className = 'search-highlight'; var middlebit = node.splitText(pos); var endbit = middlebit.splitText(pat.length); var middleclone = middlebit.cloneNode(true); spannode.appendChild(middleclone); middlebit.parentNode.replaceChild(spannode, middlebit); skip = 1; } } else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { for (var i = 0; i < node.childNodes.length; ++i) { i += innerHighlight(node.childNodes[i], pat); } } return skip; } return this.each(function() { innerHighlight(this, pat.toUpperCase()); }); }; jQuery.fn.removeHighlight = function() { return this.find("span.search-highlight").each(function() { this.parentNode.firstChild.nodeName; with (this.parentNode) { replaceChild(this.firstChild, this); normalize(); } }).end(); }; jQuery.timer = function (interval, callback) { /** * * timer() provides a cleaner way to handle intervals * * @usage * $.timer(interval, callback); * * * @example * $.timer(1000, function (timer) { * alert("hello"); * timer.stop(); * }); * @desc Show an alert box after 1 second and stop * * @example * var second = false; * $.timer(1000, function (timer) { * if (!second) { * alert('First time!'); * second = true; * timer.reset(3000); * } * else { * alert('Second time'); * timer.stop(); * } * }); * @desc Show an alert box after 1 second and show another after 3 seconds * * */ var interval = interval || 100; if (!callback) return false; _timer = function (interval, callback) { this.stop = function () { clearInterval(self.id); }; this.internalCallback = function () { callback(self); }; this.reset = function (val) { if (self.id) clearInterval(self.id); var val = val || 100; this.id = setInterval(this.internalCallback, val); }; this.interval = interval; this.id = setInterval(this.internalCallback, this.interval); var self = this; }; return new _timer(interval, callback); }; /* * * Copyright (c) 2006-2008 Sam Collett (http://www.texotela.co.uk) * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. * * Version 2.2.4 * Demo: http://www.texotela.co.uk/code/jquery/select/ * * $LastChangedDate: 2008-06-17 17:27:25 +0100 (Tue, 17 Jun 2008) $ * $Rev: 5727 $ * */ ;(function(h){h.fn.addOption=function(){var j=function(a,f,c,g){var d=document.createElement("option");d.value=f,d.text=c;var b=a.options;var e=b.length;if(!a.cache){a.cache={};for(var i=0;i=2){if(typeof(k[1])=="boolean")l=k[1];else if(typeof(k[2])=="boolean")l=k[2];if(!m){o=k[0];p=k[1]}}this.each(function(){if(this.nodeName.toLowerCase()!="select")return;if(m){for(var a in n){j(this,a,n[a],l)}}else{j(this,o,p,l)}});return this};h.fn.ajaxAddOption=function(c,g,d,b,e){if(typeof(c)!="string")return this;if(typeof(g)!="object")g={};if(typeof(d)!="boolean")d=true;this.each(function(){var f=this;h.getJSON(c,g,function(a){h(f).addOption(a,d);if(typeof b=="function"){if(typeof e=="object"){b.apply(f,e)}else{b.call(f)}}})});return this};h.fn.removeOption=function(){var d=arguments;if(d.length==0)return this;var b=typeof(d[0]);var e,i;if(b=="string"||b=="object"||b=="function"){e=d[0];if(e.constructor==Array){var j=e.length;for(var k=0;k=0;g--){if(e.constructor==RegExp){if(f[g].value.match(e)){a=true}}else if(f[g].value==e){a=true}if(a&&d[1]===true)a=f[g].selected;if(a){f[g]=null}a=false}}else{if(d[1]===true){a=f[i].selected}else{a=true}if(a){this.remove(i)}}});return this};h.fn.sortOptions=function(e){var i=h(this).selectedValues();var j=typeof(e)=="undefined"?true:!!e;this.each(function(){if(this.nodeName.toLowerCase()!="select")return;var c=this.options;var g=c.length;var d=[];for(var b=0;bo2t?-1:1}});for(var b=0;b 15){ words[i] = trim(words[i].split('').join(c)); } } node.nodeValue = words.join(' '); } } }); return this; }; })(jQuery); function wbr(string,length){ string = string.replace(/(?:<[^>]+>)|(.{20})/g,'$&'); return string.replace(/>/,'>'); } /* * Auto-growing textareas; technique ripped from Facebook */ $.fn.autogrow = function(options) { this.filter('textarea').each(function() { var $this = $(this), minHeight = $this.height(), lineHeight = $this.css('lineHeight'); var shadow = $('
    ').css({ position: 'absolute', top: -10000, left: -10000, width: $(this).width() - parseInt($this.css('paddingLeft'),10) - parseInt($this.css('paddingRight'),10), fontSize: $this.css('fontSize'), fontFamily: $this.css('fontFamily'), lineHeight: $this.css('lineHeight'), resize: 'none' }).appendTo(document.body); var update = function() { var times = function(string, number) { for (var i = 0, r = ''; i < number; i ++) r += string; return r; }; var val = this.value.replace(//g, '>') .replace(/&/g, '&') .replace(/\n$/, '
     ') .replace(/\n/g, '
    ') .replace(/ {2,}/g, function(space) { return times(' ', space.length -1) + ' ' ;}); shadow.html(val); $(this).css('height', Math.max(shadow.height() + 20, minHeight)); }; $(this).change(update).keyup(update).keydown(update); update.apply(this); }); return this; }; (function( $ ){ //Auto complete code for @mentions //To attach to a textarea add this class: autocomplete-mentions and the script below // //or just directly run $('#widget').mentions(projectId); //or add a property to the text area autocomplete-mentions-projectid="<%= as.project_id %>" // "autocomplete-mentions-projectid" => @project.id function getCaretPosition(e) { if (typeof e.selectionStart == 'number') { return e.selectionStart; } else if (document.selection) { var range = document.selection.createRange(); var rangeLength = range.text.length; range.moveStart('character', -e.value.length); return range.text.length - rangeLength; } }; function setCaretPosition(e, start, end) { if (e.createTextRange) { var r = e.createTextRange(); r.moveStart('character', start); r.moveEnd('character', (end || start)); r.select(); } else if (e.selectionStart) { e.focus(); e.setSelectionRange(start, (end || start)); } }; function getWordBeforeCaretPosition(e) { var s = e.value; var i = getCaretPosition(e) - 1; while (i >= 0 && s[i] != ' ') { i = i - 1; } return i + 1; }; function getWordBeforeCaret(e) { var p = getWordBeforeCaretPosition(e); var c = getCaretPosition(e); return e.value.substring(p, c); }; function replaceWordBeforeCaret(e, word) { var p = getWordBeforeCaretPosition(e); var c = getCaretPosition(e); e.value = e.value.substring(0, p) + word + e.value.substring(c); setCaretPosition(e, p + word.length); }; var methods = { init : function( options ) { return this.each(function(){ var $this = $(this), data = $this.data('mentions'); if (options < 0){ var projectId = $this.attr("autocomplete-mentions-projectid"); } else{ var projectId = options; } $this.bind("keydown", function(event) { if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active ) { event.preventDefault(); } }) .autocomplete({ minLength: 0, open: function(){ $(".ui-menu").width('auto'); }, source: function(request, response) { var w = getWordBeforeCaret(this.element[0]); if (w[0] != '@') { this.close(); return false; } if (typeof community_members[projectId] != "undefined") { //map the data into a response that will be understood by the autocomplete widget response($.ui.autocomplete.filter(community_members[projectId], w.substring(1, w.length))); } //get the data from the server else { $.ajax({ url: "/projects/" + projectId + "/community_members_array", dataType: "json", success: function(data) { //cache the data for later community_members[projectId] = data; //map the data into a response that will be understood by the autocomplete widget response($.ui.autocomplete.filter(community_members[projectId], w.substring(1, w.length))); } }); } }, delay: 0, position: { my: "left top", at: "right top" }, focus: function() { return false; }, search: function(event, ui) { return true; }, select: function(event, ui) { replaceWordBeforeCaret(this, '@' + ui.item.value + ' '); return false; } }) .data( "autocomplete" )._renderItem = function( ul, item ) { return $( "
  • " ) .data( "item.autocomplete", item ) .append( "") // .append( "" + item.label + "" ) // .data( "item.autocomplete", item ) // .appendTo( ul ); // }; }); }, destroy : function( ) { return this.each(function(){ var $this = $(this), data = $this.data('mentions'); // Namespacing FTW $(window).unbind('.mentions'); data.mentions.remove(); $this.removeData('mentions'); }) } }; $.fn.mentions = function( projectId ) { return methods['init'].apply( this, Array.prototype.slice.call( arguments, 0 )); }; })( jQuery ); function bind_autocomplete_mentions(){ $("input[autocomplete-mentions-projectid]").mentions(-1); $("textarea[autocomplete-mentions-projectid]").mentions(-1); if (typeof projectId != "undefined"){ $( ".autocomplete-mentions" ).mentions(projectId); } }; function bind_relations_autocomplete(projectId){ $(".issue-relation").autocomplete({ minLength: 2, // source: // "/projects/" + projectId + "/community_members_array", // source: function( request, response ) { // $.ajax({ // url: "http://ws.geonames.org/searchJSON", // dataType: "jsonp", // data: { // featureClass: "P", // style: "full", // maxRows: 12, // name_startsWith: request.term // }, // success: function( data ) { // console.log(data); // response( $.map( data.geonames, function( item ) { // return { // label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName, // value: item.name // } // })); // } // }); // }, source: function( request, response ) { $.ajax({ url: "/projects/" + projectId + "/issue_search", dataType: "json", data: { // maxRows: 12, searchTerm: request.term }, success: function( data ) { response( $.map( data, function( item ) { return { label: item.id + ' - ' + item.subject, value: item.id } })); } }); }, delay: 0 }) } function help_popup(){ $.fancybox({ 'content' : $('#help_section_container').html(), 'padding' : 0, 'margin' : 0, 'title' : 'Help Tip' // 'transitionIn' : 'elastic', // 'transitionOut' : 'elastic', // 'scrolling' : 'no' }); }; /** * Cookie plugin * * Copyright (c) 2006 Klaus Hartl (stilbuero.de) * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * */ /** * Create a cookie with the given name and value and other optional parameters. * * @example $.cookie('the_cookie', 'the_value'); * @desc Set the value of a cookie. * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); * @desc Create a cookie with all available options. * @example $.cookie('the_cookie', 'the_value'); * @desc Create a session cookie. * @example $.cookie('the_cookie', null); * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain * used when the cookie was set. * * @param String name The name of the cookie. * @param String value The value of the cookie. * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. * If set to null or omitted, the cookie will be a session cookie and will not be retained * when the the browser exits. * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will * require a secure protocol (like HTTPS). * @type undefined * * @name $.cookie * @cat Plugins/Cookie * @author Klaus Hartl/klaus.hartl@stilbuero.de */ /** * Get the value of a cookie with the given name. * * @example $.cookie('the_cookie'); * @desc Get the value of a cookie. * * @param String name The name of the cookie. * @return The value of the cookie. * @type String * * @name $.cookie * @cat Plugins/Cookie * @author Klaus Hartl/klaus.hartl@stilbuero.de */ jQuery.cookie = function(name, value, options) { if (typeof value != 'undefined') { // name and value given, set cookie options = options || {}; if (value === null) { value = ''; options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE } // CAUTION: Needed to parenthesize options.path and options.domain // in the following expressions, otherwise they evaluate to undefined // in the packed version for some reason... var path = options.path ? '; path=' + (options.path) : ''; var domain = options.domain ? '; domain=' + (options.domain) : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { // only name given, get cookie var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } }; ================================================ FILE: public/javascripts/calendar/calendar-setup.js ================================================ /* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ * --------------------------------------------------------------------------- * * The DHTML Calendar * * Details and latest version at: * http://dynarch.com/mishoo/calendar.epl * * This script is distributed under the GNU Lesser General Public License. * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html * * This file defines helper functions for setting up the calendar. They are * intended to help non-programmers get a working calendar on their site * quickly. This script should not be seen as part of the calendar. It just * shows you what one can do with the calendar, while in the same time * providing a quick and simple method for setting it up. If you need * exhaustive customization of the calendar creation process feel free to * modify this code to suit your needs (this is recommended and much better * than modifying calendar.js itself). */ // $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $ /** * This function "patches" an input field (or other element) to use a calendar * widget for date selection. * * The "params" is a single object that can have the following properties: * * prop. name | description * ------------------------------------------------------------------------------------------------- * inputField | the ID of an input field to store the date * displayArea | the ID of a DIV or other element to show the date * button | ID of a button or other element that will trigger the calendar * eventName | event that will trigger the calendar, without the "on" prefix (default: "click") * ifFormat | date format that will be stored in the input field * daFormat | the date format that will be used to display the date in displayArea * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc. * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation * range | array with 2 elements. Default: [1900, 2999] -- the range of years available * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) * onClose | function that gets called when the calendar is closed. [default] * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. * date | the date that the calendar will be initially displayed to * showsTime | default: false; if true the calendar will include a time selector * timeFormat | the time format; can be "12" or "24", default is "12" * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close * step | configures the step of the years in drop-down boxes; default: 2 * position | configures the calendar absolute position; default: null * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible * showOthers | if "true" (but default: "false") it will show days from other months too * * None of them is required, they all have default values. However, if you * pass none of "inputField", "displayArea" or "button" you'll get a warning * saying "nothing to setup". */ Calendar.setup = function (params) { function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; param_default("inputField", null); param_default("displayArea", null); param_default("button", null); param_default("eventName", "click"); param_default("ifFormat", "%Y/%m/%d"); param_default("daFormat", "%Y/%m/%d"); param_default("singleClick", true); param_default("disableFunc", null); param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined param_default("dateText", null); param_default("firstDay", null); param_default("align", "Br"); param_default("range", [1900, 2999]); param_default("weekNumbers", true); param_default("flat", null); param_default("flatCallback", null); param_default("onSelect", null); param_default("onClose", null); param_default("onUpdate", null); param_default("date", null); param_default("showsTime", false); param_default("timeFormat", "24"); param_default("electric", true); param_default("step", 2); param_default("position", null); param_default("cache", false); param_default("showOthers", false); param_default("multiple", null); var tmp = ["inputField", "displayArea", "button"]; for (var i in tmp) { if (typeof params[tmp[i]] == "string") { params[tmp[i]] = document.getElementById(params[tmp[i]]); } } if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); return false; } function onSelect(cal) { var p = cal.params; var update = (cal.dateClicked || p.electric); if (update && p.inputField) { p.inputField.value = cal.date.print(p.ifFormat); if (typeof p.inputField.onchange == "function") p.inputField.onchange(); } if (update && p.displayArea) p.displayArea.innerHTML = cal.date.print(p.daFormat); if (update && typeof p.onUpdate == "function") p.onUpdate(cal); if (update && p.flat) { if (typeof p.flatCallback == "function") p.flatCallback(cal); } if (update && p.singleClick && cal.dateClicked) cal.callCloseHandler(); }; if (params.flat != null) { if (typeof params.flat == "string") params.flat = document.getElementById(params.flat); if (!params.flat) { alert("Calendar.setup:\n Flat specified but can't find parent."); return false; } var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); cal.showsOtherMonths = params.showOthers; cal.showsTime = params.showsTime; cal.time24 = (params.timeFormat == "24"); cal.params = params; cal.weekNumbers = params.weekNumbers; cal.setRange(params.range[0], params.range[1]); cal.setDateStatusHandler(params.dateStatusFunc); cal.getDateText = params.dateText; if (params.ifFormat) { cal.setDateFormat(params.ifFormat); } if (params.inputField && typeof params.inputField.value == "string") { cal.parseDate(params.inputField.value); } cal.create(params.flat); cal.show(); return false; } var triggerEl = params.button || params.displayArea || params.inputField; triggerEl["on" + params.eventName] = function() { var dateEl = params.inputField || params.displayArea; var dateFmt = params.inputField ? params.ifFormat : params.daFormat; var mustCreate = false; var cal = window.calendar; if (dateEl) params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); if (!(cal && params.cache)) { window.calendar = cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect, params.onClose || function(cal) { cal.hide(); }); cal.showsTime = params.showsTime; cal.time24 = (params.timeFormat == "24"); cal.weekNumbers = params.weekNumbers; mustCreate = true; } else { if (params.date) cal.setDate(params.date); cal.hide(); } if (params.multiple) { cal.multiple = {}; for (var i = params.multiple.length; --i >= 0;) { var d = params.multiple[i]; var ds = d.print("%Y%m%d"); cal.multiple[ds] = d; } } cal.showsOtherMonths = params.showOthers; cal.yearStep = params.step; cal.setRange(params.range[0], params.range[1]); cal.params = params; cal.setDateStatusHandler(params.dateStatusFunc); cal.getDateText = params.dateText; cal.setDateFormat(dateFmt); if (mustCreate) cal.create(); cal.refresh(); if (!params.position) cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); else cal.showAt(params.position[0], params.position[1]); return false; }; return cal; }; ================================================ FILE: public/javascripts/calendar/calendar.js ================================================ /* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo * ----------------------------------------------------------- * * The DHTML Calendar, version 1.0 "It is happening again" * * Details and latest version at: * www.dynarch.com/projects/calendar * * This script is developed by Dynarch.com. Visit us at www.dynarch.com. * * This script is distributed under the GNU Lesser General Public License. * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html */ // $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $ /** The Calendar object constructor. */ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { // member variables this.activeDiv = null; this.currentDateEl = null; this.getDateStatus = null; this.getDateToolTip = null; this.getDateText = null; this.timeout = null; this.onSelected = onSelected || null; this.onClose = onClose || null; this.dragging = false; this.hidden = false; this.minYear = 1970; this.maxYear = 2050; this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; this.isPopup = true; this.weekNumbers = true; this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. this.showsOtherMonths = false; this.dateStr = dateStr; this.ar_days = null; this.showsTime = false; this.time24 = true; this.yearStep = 2; this.hiliteToday = true; this.multiple = null; // HTML elements this.table = null; this.element = null; this.tbody = null; this.firstdayname = null; // Combo boxes this.monthsCombo = null; this.yearsCombo = null; this.hilitedMonth = null; this.activeMonth = null; this.hilitedYear = null; this.activeYear = null; // Information this.dateClicked = false; // one-time initializations if (typeof Calendar._SDN == "undefined") { // table of short day names if (typeof Calendar._SDN_len == "undefined") Calendar._SDN_len = 3; var ar = new Array(); for (var i = 8; i > 0;) { ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); } Calendar._SDN = ar; // table of short month names if (typeof Calendar._SMN_len == "undefined") Calendar._SMN_len = 3; ar = new Array(); for (var i = 12; i > 0;) { ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); } Calendar._SMN = ar; } }; // ** constants /// "static", needed for event handlers. Calendar._C = null; /// detect a special case of "web browser" Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) ); Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); /// detect Opera browser Calendar.is_opera = /opera/i.test(navigator.userAgent); /// detect KHTML-based browsers Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); // BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate // library, at some point. Calendar.getAbsolutePos = function(el) { var SL = 0, ST = 0; var is_div = /^div$/i.test(el.tagName); if (is_div && el.scrollLeft) SL = el.scrollLeft; if (is_div && el.scrollTop) ST = el.scrollTop; var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; if (el.offsetParent) { var tmp = this.getAbsolutePos(el.offsetParent); r.x += tmp.x; r.y += tmp.y; } return r; }; Calendar.isRelated = function (el, evt) { var related = evt.relatedTarget; if (!related) { var type = evt.type; if (type == "mouseover") { related = evt.fromElement; } else if (type == "mouseout") { related = evt.toElement; } } while (related) { if (related == el) { return true; } related = related.parentNode; } return false; }; Calendar.removeClass = function(el, className) { if (!(el && el.className)) { return; } var cls = el.className.split(" "); var ar = new Array(); for (var i = cls.length; i > 0;) { if (cls[--i] != className) { ar[ar.length] = cls[i]; } } el.className = ar.join(" "); }; Calendar.addClass = function(el, className) { Calendar.removeClass(el, className); el.className += " " + className; }; // FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. Calendar.getElement = function(ev) { var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; while (f.nodeType != 1 || /^div$/i.test(f.tagName)) f = f.parentNode; return f; }; Calendar.getTargetElement = function(ev) { var f = Calendar.is_ie ? window.event.srcElement : ev.target; while (f.nodeType != 1) f = f.parentNode; return f; }; Calendar.stopEvent = function(ev) { ev || (ev = window.event); if (Calendar.is_ie) { ev.cancelBubble = true; ev.returnValue = false; } else { ev.preventDefault(); ev.stopPropagation(); } return false; }; Calendar.addEvent = function(el, evname, func) { if (el.attachEvent) { // IE el.attachEvent("on" + evname, func); } else if (el.addEventListener) { // Gecko / W3C el.addEventListener(evname, func, true); } else { el["on" + evname] = func; } }; Calendar.removeEvent = function(el, evname, func) { if (el.detachEvent) { // IE el.detachEvent("on" + evname, func); } else if (el.removeEventListener) { // Gecko / W3C el.removeEventListener(evname, func, true); } else { el["on" + evname] = null; } }; Calendar.createElement = function(type, parent) { var el = null; if (document.createElementNS) { // use the XHTML namespace; IE won't normally get here unless // _they_ "fix" the DOM2 implementation. el = document.createElementNS("http://www.w3.org/1999/xhtml", type); } else { el = document.createElement(type); } if (typeof parent != "undefined") { parent.appendChild(el); } return el; }; // END: UTILITY FUNCTIONS // BEGIN: CALENDAR STATIC FUNCTIONS /** Internal -- adds a set of events to make some element behave like a button. */ Calendar._add_evs = function(el) { with (Calendar) { addEvent(el, "mouseover", dayMouseOver); addEvent(el, "mousedown", dayMouseDown); addEvent(el, "mouseout", dayMouseOut); if (is_ie) { addEvent(el, "dblclick", dayMouseDblClick); el.setAttribute("unselectable", true); } } }; Calendar.findMonth = function(el) { if (typeof el.month != "undefined") { return el; } else if (typeof el.parentNode.month != "undefined") { return el.parentNode; } return null; }; Calendar.findYear = function(el) { if (typeof el.year != "undefined") { return el; } else if (typeof el.parentNode.year != "undefined") { return el.parentNode; } return null; }; Calendar.showMonthsCombo = function () { var cal = Calendar._C; if (!cal) { return false; } var cal = cal; var cd = cal.activeDiv; var mc = cal.monthsCombo; if (cal.hilitedMonth) { Calendar.removeClass(cal.hilitedMonth, "hilite"); } if (cal.activeMonth) { Calendar.removeClass(cal.activeMonth, "active"); } var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; Calendar.addClass(mon, "active"); cal.activeMonth = mon; var s = mc.style; s.display = "block"; if (cd.navtype < 0) s.left = cd.offsetLeft + "px"; else { var mcw = mc.offsetWidth; if (typeof mcw == "undefined") // Konqueror brain-dead techniques mcw = 50; s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; } s.top = (cd.offsetTop + cd.offsetHeight) + "px"; }; Calendar.showYearsCombo = function (fwd) { var cal = Calendar._C; if (!cal) { return false; } var cal = cal; var cd = cal.activeDiv; var yc = cal.yearsCombo; if (cal.hilitedYear) { Calendar.removeClass(cal.hilitedYear, "hilite"); } if (cal.activeYear) { Calendar.removeClass(cal.activeYear, "active"); } cal.activeYear = null; var Y = cal.date.getFullYear() + (fwd ? 1 : -1); var yr = yc.firstChild; var show = false; for (var i = 12; i > 0; --i) { if (Y >= cal.minYear && Y <= cal.maxYear) { yr.innerHTML = Y; yr.year = Y; yr.style.display = "block"; show = true; } else { yr.style.display = "none"; } yr = yr.nextSibling; Y += fwd ? cal.yearStep : -cal.yearStep; } if (show) { var s = yc.style; s.display = "block"; if (cd.navtype < 0) s.left = cd.offsetLeft + "px"; else { var ycw = yc.offsetWidth; if (typeof ycw == "undefined") // Konqueror brain-dead techniques ycw = 50; s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; } s.top = (cd.offsetTop + cd.offsetHeight) + "px"; } }; // event handlers Calendar.tableMouseUp = function(ev) { var cal = Calendar._C; if (!cal) { return false; } if (cal.timeout) { clearTimeout(cal.timeout); } var el = cal.activeDiv; if (!el) { return false; } var target = Calendar.getTargetElement(ev); ev || (ev = window.event); Calendar.removeClass(el, "active"); if (target == el || target.parentNode == el) { Calendar.cellClick(el, ev); } var mon = Calendar.findMonth(target); var date = null; if (mon) { date = new Date(cal.date); if (mon.month != date.getMonth()) { date.setMonth(mon.month); cal.setDate(date); cal.dateClicked = false; cal.callHandler(); } } else { var year = Calendar.findYear(target); if (year) { date = new Date(cal.date); if (year.year != date.getFullYear()) { date.setFullYear(year.year); cal.setDate(date); cal.dateClicked = false; cal.callHandler(); } } } with (Calendar) { removeEvent(document, "mouseup", tableMouseUp); removeEvent(document, "mouseover", tableMouseOver); removeEvent(document, "mousemove", tableMouseOver); cal._hideCombos(); _C = null; return stopEvent(ev); } }; Calendar.tableMouseOver = function (ev) { var cal = Calendar._C; if (!cal) { return; } var el = cal.activeDiv; var target = Calendar.getTargetElement(ev); if (target == el || target.parentNode == el) { Calendar.addClass(el, "hilite active"); Calendar.addClass(el.parentNode, "rowhilite"); } else { if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) Calendar.removeClass(el, "active"); Calendar.removeClass(el, "hilite"); Calendar.removeClass(el.parentNode, "rowhilite"); } ev || (ev = window.event); if (el.navtype == 50 && target != el) { var pos = Calendar.getAbsolutePos(el); var w = el.offsetWidth; var x = ev.clientX; var dx; var decrease = true; if (x > pos.x + w) { dx = x - pos.x - w; decrease = false; } else dx = pos.x - x; if (dx < 0) dx = 0; var range = el._range; var current = el._current; var count = Math.floor(dx / 10) % range.length; for (var i = range.length; --i >= 0;) if (range[i] == current) break; while (count-- > 0) if (decrease) { if (--i < 0) i = range.length - 1; } else if ( ++i >= range.length ) i = 0; var newval = range[i]; el.innerHTML = newval; cal.onUpdateTime(); } var mon = Calendar.findMonth(target); if (mon) { if (mon.month != cal.date.getMonth()) { if (cal.hilitedMonth) { Calendar.removeClass(cal.hilitedMonth, "hilite"); } Calendar.addClass(mon, "hilite"); cal.hilitedMonth = mon; } else if (cal.hilitedMonth) { Calendar.removeClass(cal.hilitedMonth, "hilite"); } } else { if (cal.hilitedMonth) { Calendar.removeClass(cal.hilitedMonth, "hilite"); } var year = Calendar.findYear(target); if (year) { if (year.year != cal.date.getFullYear()) { if (cal.hilitedYear) { Calendar.removeClass(cal.hilitedYear, "hilite"); } Calendar.addClass(year, "hilite"); cal.hilitedYear = year; } else if (cal.hilitedYear) { Calendar.removeClass(cal.hilitedYear, "hilite"); } } else if (cal.hilitedYear) { Calendar.removeClass(cal.hilitedYear, "hilite"); } } return Calendar.stopEvent(ev); }; Calendar.tableMouseDown = function (ev) { if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { return Calendar.stopEvent(ev); } }; Calendar.calDragIt = function (ev) { var cal = Calendar._C; if (!(cal && cal.dragging)) { return false; } var posX; var posY; if (Calendar.is_ie) { posY = window.event.clientY + document.body.scrollTop; posX = window.event.clientX + document.body.scrollLeft; } else { posX = ev.pageX; posY = ev.pageY; } cal.hideShowCovered(); var st = cal.element.style; st.left = (posX - cal.xOffs) + "px"; st.top = (posY - cal.yOffs) + "px"; return Calendar.stopEvent(ev); }; Calendar.calDragEnd = function (ev) { var cal = Calendar._C; if (!cal) { return false; } cal.dragging = false; with (Calendar) { removeEvent(document, "mousemove", calDragIt); removeEvent(document, "mouseup", calDragEnd); tableMouseUp(ev); } cal.hideShowCovered(); }; Calendar.dayMouseDown = function(ev) { var el = Calendar.getElement(ev); if (el.disabled) { return false; } var cal = el.calendar; cal.activeDiv = el; Calendar._C = cal; if (el.navtype != 300) with (Calendar) { if (el.navtype == 50) { el._current = el.innerHTML; addEvent(document, "mousemove", tableMouseOver); } else addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); addClass(el, "hilite active"); addEvent(document, "mouseup", tableMouseUp); } else if (cal.isPopup) { cal._dragStart(ev); } if (el.navtype == -1 || el.navtype == 1) { if (cal.timeout) clearTimeout(cal.timeout); cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); } else if (el.navtype == -2 || el.navtype == 2) { if (cal.timeout) clearTimeout(cal.timeout); cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); } else { cal.timeout = null; } return Calendar.stopEvent(ev); }; Calendar.dayMouseDblClick = function(ev) { Calendar.cellClick(Calendar.getElement(ev), ev || window.event); if (Calendar.is_ie) { document.selection.empty(); } }; Calendar.dayMouseOver = function(ev) { var el = Calendar.getElement(ev); if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { return false; } if (el.ttip) { if (el.ttip.substr(0, 1) == "_") { el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); } el.calendar.tooltips.innerHTML = el.ttip; } if (el.navtype != 300) { Calendar.addClass(el, "hilite"); if (el.caldate) { Calendar.addClass(el.parentNode, "rowhilite"); } } return Calendar.stopEvent(ev); }; Calendar.dayMouseOut = function(ev) { with (Calendar) { var el = getElement(ev); if (isRelated(el, ev) || _C || el.disabled) return false; removeClass(el, "hilite"); if (el.caldate) removeClass(el.parentNode, "rowhilite"); if (el.calendar) el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; return stopEvent(ev); } }; /** * A generic "click" handler :) handles all types of buttons defined in this * calendar. */ Calendar.cellClick = function(el, ev) { var cal = el.calendar; var closing = false; var newdate = false; var date = null; if (typeof el.navtype == "undefined") { if (cal.currentDateEl) { Calendar.removeClass(cal.currentDateEl, "selected"); Calendar.addClass(el, "selected"); closing = (cal.currentDateEl == el); if (!closing) { cal.currentDateEl = el; } } cal.date.setDateOnly(el.caldate); date = cal.date; var other_month = !(cal.dateClicked = !el.otherMonth); if (!other_month && !cal.currentDateEl) cal._toggleMultipleDate(new Date(date)); else newdate = !el.disabled; // a date was clicked if (other_month) cal._init(cal.firstDayOfWeek, date); } else { if (el.navtype == 200) { Calendar.removeClass(el, "hilite"); cal.callCloseHandler(); return; } date = new Date(cal.date); if (el.navtype == 0) date.setDateOnly(new Date()); // TODAY // unless "today" was clicked, we assume no date was clicked so // the selected handler will know not to close the calenar when // in single-click mode. // cal.dateClicked = (el.navtype == 0); cal.dateClicked = false; var year = date.getFullYear(); var mon = date.getMonth(); function setMonth(m) { var day = date.getDate(); var max = date.getMonthDays(m); if (day > max) { date.setDate(max); } date.setMonth(m); }; switch (el.navtype) { case 400: Calendar.removeClass(el, "hilite"); var text = Calendar._TT["ABOUT"]; if (typeof text != "undefined") { text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; } else { // FIXME: this should be removed as soon as lang files get updated! text = "Help and about box text is not translated into this language.\n" + "If you know this language and you feel generous please update\n" + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + "and send it back to to get it into the distribution ;-)\n\n" + "Thank you!\n" + "http://dynarch.com/mishoo/calendar.epl\n"; } alert(text); return; case -2: if (year > cal.minYear) { date.setFullYear(year - 1); } break; case -1: if (mon > 0) { setMonth(mon - 1); } else if (year-- > cal.minYear) { date.setFullYear(year); setMonth(11); } break; case 1: if (mon < 11) { setMonth(mon + 1); } else if (year < cal.maxYear) { date.setFullYear(year + 1); setMonth(0); } break; case 2: if (year < cal.maxYear) { date.setFullYear(year + 1); } break; case 100: cal.setFirstDayOfWeek(el.fdow); return; case 50: var range = el._range; var current = el.innerHTML; for (var i = range.length; --i >= 0;) if (range[i] == current) break; if (ev && ev.shiftKey) { if (--i < 0) i = range.length - 1; } else if ( ++i >= range.length ) i = 0; var newval = range[i]; el.innerHTML = newval; cal.onUpdateTime(); return; case 0: // TODAY will bring us here if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { return false; } break; } if (!date.equalsTo(cal.date)) { cal.setDate(date); newdate = true; } else if (el.navtype == 0) newdate = closing = true; } if (newdate) { ev && cal.callHandler(); } if (closing) { Calendar.removeClass(el, "hilite"); ev && cal.callCloseHandler(); } }; // END: CALENDAR STATIC FUNCTIONS // BEGIN: CALENDAR OBJECT FUNCTIONS /** * This function creates the calendar inside the given parent. If _par is * null than it creates a popup calendar inside the BODY element. If _par is * an element, be it BODY, then it creates a non-popup calendar (still * hidden). Some properties need to be set before calling this function. */ Calendar.prototype.create = function (_par) { var parent = null; if (! _par) { // default parent is the document body, in which case we create // a popup calendar. parent = document.getElementsByTagName("body")[0]; this.isPopup = true; } else { parent = _par; this.isPopup = false; } this.date = this.dateStr ? new Date(this.dateStr) : new Date(); var table = Calendar.createElement("table"); this.table = table; table.cellSpacing = 0; table.cellPadding = 0; table.calendar = this; Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); var div = Calendar.createElement("div"); this.element = div; div.className = "calendar"; if (this.isPopup) { div.style.position = "absolute"; div.style.display = "none"; } div.appendChild(table); var thead = Calendar.createElement("thead", table); var cell = null; var row = null; var cal = this; var hh = function (text, cs, navtype) { cell = Calendar.createElement("td", row); cell.colSpan = cs; cell.className = "button"; if (navtype != 0 && Math.abs(navtype) <= 2) cell.className += " nav"; Calendar._add_evs(cell); cell.calendar = cal; cell.navtype = navtype; cell.innerHTML = "
    " + text + "
    "; return cell; }; row = Calendar.createElement("tr", thead); var title_length = 6; (this.isPopup) && --title_length; (this.weekNumbers) && ++title_length; hh("?", 1, 400).ttip = Calendar._TT["INFO"]; this.title = hh("", title_length, 300); this.title.className = "title"; if (this.isPopup) { this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; this.title.style.cursor = "move"; hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; } row = Calendar.createElement("tr", thead); row.className = "headrow"; this._nav_py = hh("«", 1, -2); this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; this._nav_pm = hh("‹", 1, -1); this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); this._nav_now.ttip = Calendar._TT["GO_TODAY"]; this._nav_nm = hh("›", 1, 1); this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; this._nav_ny = hh("»", 1, 2); this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; // day names row = Calendar.createElement("tr", thead); row.className = "daynames"; if (this.weekNumbers) { cell = Calendar.createElement("td", row); cell.className = "name wn"; cell.innerHTML = Calendar._TT["WK"]; } for (var i = 7; i > 0; --i) { cell = Calendar.createElement("td", row); if (!i) { cell.navtype = 100; cell.calendar = this; Calendar._add_evs(cell); } } this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; this._displayWeekdays(); var tbody = Calendar.createElement("tbody", table); this.tbody = tbody; for (i = 6; i > 0; --i) { row = Calendar.createElement("tr", tbody); if (this.weekNumbers) { cell = Calendar.createElement("td", row); } for (var j = 7; j > 0; --j) { cell = Calendar.createElement("td", row); cell.calendar = this; Calendar._add_evs(cell); } } if (this.showsTime) { row = Calendar.createElement("tr", tbody); row.className = "time"; cell = Calendar.createElement("td", row); cell.className = "time"; cell.colSpan = 2; cell.innerHTML = Calendar._TT["TIME"] || " "; cell = Calendar.createElement("td", row); cell.className = "time"; cell.colSpan = this.weekNumbers ? 4 : 3; (function(){ function makeTimePart(className, init, range_start, range_end) { var part = Calendar.createElement("span", cell); part.className = className; part.innerHTML = init; part.calendar = cal; part.ttip = Calendar._TT["TIME_PART"]; part.navtype = 50; part._range = []; if (typeof range_start != "number") part._range = range_start; else { for (var i = range_start; i <= range_end; ++i) { var txt; if (i < 10 && range_end >= 10) txt = '0' + i; else txt = '' + i; part._range[part._range.length] = txt; } } Calendar._add_evs(part); return part; }; var hrs = cal.date.getHours(); var mins = cal.date.getMinutes(); var t12 = !cal.time24; var pm = (hrs > 12); if (t12 && pm) hrs -= 12; var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); var span = Calendar.createElement("span", cell); span.innerHTML = ":"; span.className = "colon"; var M = makeTimePart("minute", mins, 0, 59); var AP = null; cell = Calendar.createElement("td", row); cell.className = "time"; cell.colSpan = 2; if (t12) AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); else cell.innerHTML = " "; cal.onSetTime = function() { var pm, hrs = this.date.getHours(), mins = this.date.getMinutes(); if (t12) { pm = (hrs >= 12); if (pm) hrs -= 12; if (hrs == 0) hrs = 12; AP.innerHTML = pm ? "pm" : "am"; } H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; M.innerHTML = (mins < 10) ? ("0" + mins) : mins; }; cal.onUpdateTime = function() { var date = this.date; var h = parseInt(H.innerHTML, 10); if (t12) { if (/pm/i.test(AP.innerHTML) && h < 12) h += 12; else if (/am/i.test(AP.innerHTML) && h == 12) h = 0; } var d = date.getDate(); var m = date.getMonth(); var y = date.getFullYear(); date.setHours(h); date.setMinutes(parseInt(M.innerHTML, 10)); date.setFullYear(y); date.setMonth(m); date.setDate(d); this.dateClicked = false; this.callHandler(); }; })(); } else { this.onSetTime = this.onUpdateTime = function() {}; } var tfoot = Calendar.createElement("tfoot", table); row = Calendar.createElement("tr", tfoot); row.className = "footrow"; cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); cell.className = "ttip"; if (this.isPopup) { cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; cell.style.cursor = "move"; } this.tooltips = cell; div = Calendar.createElement("div", this.element); this.monthsCombo = div; div.className = "combo"; for (i = 0; i < Calendar._MN.length; ++i) { var mn = Calendar.createElement("div"); mn.className = Calendar.is_ie ? "label-IEfix" : "label"; mn.month = i; mn.innerHTML = Calendar._SMN[i]; div.appendChild(mn); } div = Calendar.createElement("div", this.element); this.yearsCombo = div; div.className = "combo"; for (i = 12; i > 0; --i) { var yr = Calendar.createElement("div"); yr.className = Calendar.is_ie ? "label-IEfix" : "label"; div.appendChild(yr); } this._init(this.firstDayOfWeek, this.date); parent.appendChild(this.element); }; /** keyboard navigation, only for popup calendars */ Calendar._keyEvent = function(ev) { var cal = window._dynarch_popupCalendar; if (!cal || cal.multiple) return false; (Calendar.is_ie) && (ev = window.event); var act = (Calendar.is_ie || ev.type == "keypress"), K = ev.keyCode; if (ev.ctrlKey) { switch (K) { case 37: // KEY left act && Calendar.cellClick(cal._nav_pm); break; case 38: // KEY up act && Calendar.cellClick(cal._nav_py); break; case 39: // KEY right act && Calendar.cellClick(cal._nav_nm); break; case 40: // KEY down act && Calendar.cellClick(cal._nav_ny); break; default: return false; } } else switch (K) { case 32: // KEY space (now) Calendar.cellClick(cal._nav_now); break; case 27: // KEY esc act && cal.callCloseHandler(); break; case 37: // KEY left case 38: // KEY up case 39: // KEY right case 40: // KEY down if (act) { var prev, x, y, ne, el, step; prev = K == 37 || K == 38; step = (K == 37 || K == 39) ? 1 : 7; function setVars() { el = cal.currentDateEl; var p = el.pos; x = p & 15; y = p >> 4; ne = cal.ar_days[y][x]; };setVars(); function prevMonth() { var date = new Date(cal.date); date.setDate(date.getDate() - step); cal.setDate(date); }; function nextMonth() { var date = new Date(cal.date); date.setDate(date.getDate() + step); cal.setDate(date); }; while (1) { switch (K) { case 37: // KEY left if (--x >= 0) ne = cal.ar_days[y][x]; else { x = 6; K = 38; continue; } break; case 38: // KEY up if (--y >= 0) ne = cal.ar_days[y][x]; else { prevMonth(); setVars(); } break; case 39: // KEY right if (++x < 7) ne = cal.ar_days[y][x]; else { x = 0; K = 40; continue; } break; case 40: // KEY down if (++y < cal.ar_days.length) ne = cal.ar_days[y][x]; else { nextMonth(); setVars(); } break; } break; } if (ne) { if (!ne.disabled) Calendar.cellClick(ne); else if (prev) prevMonth(); else nextMonth(); } } break; case 13: // KEY enter if (act) Calendar.cellClick(cal.currentDateEl, ev); break; default: return false; } return Calendar.stopEvent(ev); }; /** * (RE)Initializes the calendar to the given date and firstDayOfWeek */ Calendar.prototype._init = function (firstDayOfWeek, date) { var today = new Date(), TY = today.getFullYear(), TM = today.getMonth(), TD = today.getDate(); this.table.style.visibility = "hidden"; var year = date.getFullYear(); if (year < this.minYear) { year = this.minYear; date.setFullYear(year); } else if (year > this.maxYear) { year = this.maxYear; date.setFullYear(year); } this.firstDayOfWeek = firstDayOfWeek; this.date = new Date(date); var month = date.getMonth(); var mday = date.getDate(); var no_days = date.getMonthDays(); // calendar voodoo for computing the first day that would actually be // displayed in the calendar, even if it's from the previous month. // WARNING: this is magic. ;-) date.setDate(1); var day1 = (date.getDay() - this.firstDayOfWeek) % 7; if (day1 < 0) day1 += 7; date.setDate(-day1); date.setDate(date.getDate() + 1); var row = this.tbody.firstChild; var MN = Calendar._SMN[month]; var ar_days = this.ar_days = new Array(); var weekend = Calendar._TT["WEEKEND"]; var dates = this.multiple ? (this.datesCells = {}) : null; for (var i = 0; i < 6; ++i, row = row.nextSibling) { var cell = row.firstChild; if (this.weekNumbers) { cell.className = "day wn"; cell.innerHTML = date.getWeekNumber(); cell = cell.nextSibling; } row.className = "daysrow"; var hasdays = false, iday, dpos = ar_days[i] = []; for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { iday = date.getDate(); var wday = date.getDay(); cell.className = "day"; cell.pos = i << 4 | j; dpos[j] = cell; var current_month = (date.getMonth() == month); if (!current_month) { if (this.showsOtherMonths) { cell.className += " othermonth"; cell.otherMonth = true; } else { cell.className = "emptycell"; cell.innerHTML = " "; cell.disabled = true; continue; } } else { cell.otherMonth = false; hasdays = true; } cell.disabled = false; cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; if (dates) dates[date.print("%Y%m%d")] = cell; if (this.getDateStatus) { var status = this.getDateStatus(date, year, month, iday); if (this.getDateToolTip) { var toolTip = this.getDateToolTip(date, year, month, iday); if (toolTip) cell.title = toolTip; } if (status === true) { cell.className += " disabled"; cell.disabled = true; } else { if (/disabled/i.test(status)) cell.disabled = true; cell.className += " " + status; } } if (!cell.disabled) { cell.caldate = new Date(date); cell.ttip = "_"; if (!this.multiple && current_month && iday == mday && this.hiliteToday) { cell.className += " selected"; this.currentDateEl = cell; } if (date.getFullYear() == TY && date.getMonth() == TM && iday == TD) { cell.className += " today"; cell.ttip += Calendar._TT["PART_TODAY"]; } if (weekend.indexOf(wday.toString()) != -1) cell.className += cell.otherMonth ? " oweekend" : " weekend"; } } if (!(hasdays || this.showsOtherMonths)) row.className = "emptyrow"; } this.title.innerHTML = Calendar._MN[month] + ", " + year; this.onSetTime(); this.table.style.visibility = "visible"; this._initMultipleDates(); // PROFILE // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; }; Calendar.prototype._initMultipleDates = function() { if (this.multiple) { for (var i in this.multiple) { var cell = this.datesCells[i]; var d = this.multiple[i]; if (!d) continue; if (cell) cell.className += " selected"; } } }; Calendar.prototype._toggleMultipleDate = function(date) { if (this.multiple) { var ds = date.print("%Y%m%d"); var cell = this.datesCells[ds]; if (cell) { var d = this.multiple[ds]; if (!d) { Calendar.addClass(cell, "selected"); this.multiple[ds] = date; } else { Calendar.removeClass(cell, "selected"); delete this.multiple[ds]; } } } }; Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { this.getDateToolTip = unaryFunction; }; /** * Calls _init function above for going to a certain date (but only if the * date is different than the currently selected one). */ Calendar.prototype.setDate = function (date) { if (!date.equalsTo(this.date)) { this._init(this.firstDayOfWeek, date); } }; /** * Refreshes the calendar. Useful if the "disabledHandler" function is * dynamic, meaning that the list of disabled date can change at runtime. * Just * call this function if you think that the list of disabled dates * should * change. */ Calendar.prototype.refresh = function () { this._init(this.firstDayOfWeek, this.date); }; /** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { this._init(firstDayOfWeek, this.date); this._displayWeekdays(); }; /** * Allows customization of what dates are enabled. The "unaryFunction" * parameter must be a function object that receives the date (as a JS Date * object) and returns a boolean value. If the returned value is true then * the passed date will be marked as disabled. */ Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { this.getDateStatus = unaryFunction; }; /** Customization of allowed year range for the calendar. */ Calendar.prototype.setRange = function (a, z) { this.minYear = a; this.maxYear = z; }; /** Calls the first user handler (selectedHandler). */ Calendar.prototype.callHandler = function () { if (this.onSelected) { this.onSelected(this, this.date.print(this.dateFormat)); } }; /** Calls the second user handler (closeHandler). */ Calendar.prototype.callCloseHandler = function () { if (this.onClose) { this.onClose(this); } this.hideShowCovered(); }; /** Removes the calendar object from the DOM tree and destroys it. */ Calendar.prototype.destroy = function () { var el = this.element.parentNode; el.removeChild(this.element); Calendar._C = null; window._dynarch_popupCalendar = null; }; /** * Moves the calendar element to a different section in the DOM tree (changes * its parent). */ Calendar.prototype.reparent = function (new_parent) { var el = this.element; el.parentNode.removeChild(el); new_parent.appendChild(el); }; // This gets called when the user presses a mouse button anywhere in the // document, if the calendar is shown. If the click was outside the open // calendar this function closes it. Calendar._checkCalendar = function(ev) { var calendar = window._dynarch_popupCalendar; if (!calendar) { return false; } var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); for (; el != null && el != calendar.element; el = el.parentNode); if (el == null) { // calls closeHandler which should hide the calendar. window._dynarch_popupCalendar.callCloseHandler(); return Calendar.stopEvent(ev); } }; /** Shows the calendar. */ Calendar.prototype.show = function () { var rows = this.table.getElementsByTagName("tr"); for (var i = rows.length; i > 0;) { var row = rows[--i]; Calendar.removeClass(row, "rowhilite"); var cells = row.getElementsByTagName("td"); for (var j = cells.length; j > 0;) { var cell = cells[--j]; Calendar.removeClass(cell, "hilite"); Calendar.removeClass(cell, "active"); } } this.element.style.display = "block"; this.hidden = false; if (this.isPopup) { window._dynarch_popupCalendar = this; Calendar.addEvent(document, "keydown", Calendar._keyEvent); Calendar.addEvent(document, "keypress", Calendar._keyEvent); Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); } this.hideShowCovered(); }; /** * Hides the calendar. Also removes any "hilite" from the class of any TD * element. */ Calendar.prototype.hide = function () { if (this.isPopup) { Calendar.removeEvent(document, "keydown", Calendar._keyEvent); Calendar.removeEvent(document, "keypress", Calendar._keyEvent); Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); } this.element.style.display = "none"; this.hidden = true; this.hideShowCovered(); }; /** * Shows the calendar at a given absolute position (beware that, depending on * the calendar element style -- position property -- this might be relative * to the parent's containing rectangle). */ Calendar.prototype.showAt = function (x, y) { var s = this.element.style; s.left = x + "px"; s.top = y + "px"; this.show(); }; /** Shows the calendar near a given element. */ Calendar.prototype.showAtElement = function (el, opts) { var self = this; var p = Calendar.getAbsolutePos(el); if (!opts || typeof opts != "string") { this.showAt(p.x, p.y + el.offsetHeight); return true; } function fixPosition(box) { if (box.x < 0) box.x = 0; if (box.y < 0) box.y = 0; var cp = document.createElement("div"); var s = cp.style; s.position = "absolute"; s.right = s.bottom = s.width = s.height = "0px"; document.body.appendChild(cp); var br = Calendar.getAbsolutePos(cp); document.body.removeChild(cp); if (Calendar.is_ie) { br.y += document.body.scrollTop; br.x += document.body.scrollLeft; } else { br.y += window.scrollY; br.x += window.scrollX; } var tmp = box.x + box.width - br.x; if (tmp > 0) box.x -= tmp; tmp = box.y + box.height - br.y; if (tmp > 0) box.y -= tmp; }; this.element.style.display = "block"; Calendar.continuation_for_the_fucking_khtml_browser = function() { var w = self.element.offsetWidth; var h = self.element.offsetHeight; self.element.style.display = "none"; var valign = opts.substr(0, 1); var halign = "l"; if (opts.length > 1) { halign = opts.substr(1, 1); } // vertical alignment switch (valign) { case "T": p.y -= h; break; case "B": p.y += el.offsetHeight; break; case "C": p.y += (el.offsetHeight - h) / 2; break; case "t": p.y += el.offsetHeight - h; break; case "b": break; // already there } // horizontal alignment switch (halign) { case "L": p.x -= w; break; case "R": p.x += el.offsetWidth; break; case "C": p.x += (el.offsetWidth - w) / 2; break; case "l": p.x += el.offsetWidth - w; break; case "r": break; // already there } p.width = w; p.height = h + 40; self.monthsCombo.style.display = "none"; fixPosition(p); self.showAt(p.x, p.y); }; if (Calendar.is_khtml) setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); else Calendar.continuation_for_the_fucking_khtml_browser(); }; /** Customizes the date format. */ Calendar.prototype.setDateFormat = function (str) { this.dateFormat = str; }; /** Customizes the tooltip date format. */ Calendar.prototype.setTtDateFormat = function (str) { this.ttDateFormat = str; }; /** * Tries to identify the date represented in a string. If successful it also * calls this.setDate which moves the calendar to the given date. */ Calendar.prototype.parseDate = function(str, fmt) { if (!fmt) fmt = this.dateFormat; this.setDate(Date.parseDate(str, fmt)); }; Calendar.prototype.hideShowCovered = function () { if (!Calendar.is_ie && !Calendar.is_opera) return; function getVisib(obj){ var value = obj.style.visibility; if (!value) { if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C if (!Calendar.is_khtml) value = document.defaultView. getComputedStyle(obj, "").getPropertyValue("visibility"); else value = ''; } else if (obj.currentStyle) { // IE value = obj.currentStyle.visibility; } else value = ''; } return value; }; var tags = new Array("applet", "iframe", "select"); var el = this.element; var p = Calendar.getAbsolutePos(el); var EX1 = p.x; var EX2 = el.offsetWidth + EX1; var EY1 = p.y; var EY2 = el.offsetHeight + EY1; for (var k = tags.length; k > 0; ) { var ar = document.getElementsByTagName(tags[--k]); var cc = null; for (var i = ar.length; i > 0;) { cc = ar[--i]; p = Calendar.getAbsolutePos(cc); var CX1 = p.x; var CX2 = cc.offsetWidth + CX1; var CY1 = p.y; var CY2 = cc.offsetHeight + CY1; if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { if (!cc.__msh_save_visibility) { cc.__msh_save_visibility = getVisib(cc); } cc.style.visibility = cc.__msh_save_visibility; } else { if (!cc.__msh_save_visibility) { cc.__msh_save_visibility = getVisib(cc); } cc.style.visibility = "hidden"; } } } }; /** Internal function; it displays the bar with the names of the weekday. */ Calendar.prototype._displayWeekdays = function () { var fdow = this.firstDayOfWeek; var cell = this.firstdayname; var weekend = Calendar._TT["WEEKEND"]; for (var i = 0; i < 7; ++i) { cell.className = "day name"; var realday = (i + fdow) % 7; if (i) { cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); cell.navtype = 100; cell.calendar = this; cell.fdow = realday; Calendar._add_evs(cell); } if (weekend.indexOf(realday.toString()) != -1) { Calendar.addClass(cell, "weekend"); } cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; cell = cell.nextSibling; } }; /** Internal function. Hides all combo boxes that might be displayed. */ Calendar.prototype._hideCombos = function () { this.monthsCombo.style.display = "none"; this.yearsCombo.style.display = "none"; }; /** Internal function. Starts dragging the element. */ Calendar.prototype._dragStart = function (ev) { if (this.dragging) { return; } this.dragging = true; var posX; var posY; if (Calendar.is_ie) { posY = window.event.clientY + document.body.scrollTop; posX = window.event.clientX + document.body.scrollLeft; } else { posY = ev.clientY + window.scrollY; posX = ev.clientX + window.scrollX; } var st = this.element.style; this.xOffs = posX - parseInt(st.left); this.yOffs = posY - parseInt(st.top); with (Calendar) { addEvent(document, "mousemove", calDragIt); addEvent(document, "mouseup", calDragEnd); } }; // BEGIN: DATE OBJECT PATCHES /** Adds the number of days array to the Date object. */ Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); /** Constants used for time computations */ Date.SECOND = 1000 /* milliseconds */; Date.MINUTE = 60 * Date.SECOND; Date.HOUR = 60 * Date.MINUTE; Date.DAY = 24 * Date.HOUR; Date.WEEK = 7 * Date.DAY; Date.parseDate = function(str, fmt) { var today = new Date(); var y = 0; var m = -1; var d = 0; var a = str.split(/\W+/); var b = fmt.match(/%./g); var i = 0, j = 0; var hr = 0; var min = 0; for (i = 0; i < a.length; ++i) { if (!a[i]) continue; switch (b[i]) { case "%d": case "%e": d = parseInt(a[i], 10); break; case "%m": m = parseInt(a[i], 10) - 1; break; case "%Y": case "%y": y = parseInt(a[i], 10); (y < 100) && (y += (y > 29) ? 1900 : 2000); break; case "%b": case "%B": for (j = 0; j < 12; ++j) { if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } } break; case "%H": case "%I": case "%k": case "%l": hr = parseInt(a[i], 10); break; case "%P": case "%p": if (/pm/i.test(a[i]) && hr < 12) hr += 12; else if (/am/i.test(a[i]) && hr >= 12) hr -= 12; break; case "%M": min = parseInt(a[i], 10); break; } } if (isNaN(y)) y = today.getFullYear(); if (isNaN(m)) m = today.getMonth(); if (isNaN(d)) d = today.getDate(); if (isNaN(hr)) hr = today.getHours(); if (isNaN(min)) min = today.getMinutes(); if (y != 0 && m != -1 && d != 0) return new Date(y, m, d, hr, min, 0); y = 0; m = -1; d = 0; for (i = 0; i < a.length; ++i) { if (a[i].search(/[a-zA-Z]+/) != -1) { var t = -1; for (j = 0; j < 12; ++j) { if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } } if (t != -1) { if (m != -1) { d = m+1; } m = t; } } else if (parseInt(a[i], 10) <= 12 && m == -1) { m = a[i]-1; } else if (parseInt(a[i], 10) > 31 && y == 0) { y = parseInt(a[i], 10); (y < 100) && (y += (y > 29) ? 1900 : 2000); } else if (d == 0) { d = a[i]; } } if (y == 0) y = today.getFullYear(); if (m != -1 && d != 0) return new Date(y, m, d, hr, min, 0); return today; }; /** Returns the number of days in the current month */ Date.prototype.getMonthDays = function(month) { var year = this.getFullYear(); if (typeof month == "undefined") { month = this.getMonth(); } if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { return 29; } else { return Date._MD[month]; } }; /** Returns the number of day in the year. */ Date.prototype.getDayOfYear = function() { var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0); var time = now - then; return Math.floor(time / Date.DAY); }; /** Returns the number of the week in year, as defined in ISO 8601. */ Date.prototype.getWeekNumber = function() { var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); var DoW = d.getDay(); d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu var ms = d.valueOf(); // GMT d.setMonth(0); d.setDate(4); // Thu in Week 1 return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; }; /** Checks date and time equality */ Date.prototype.equalsTo = function(date) { return ((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && (this.getDate() == date.getDate()) && (this.getHours() == date.getHours()) && (this.getMinutes() == date.getMinutes())); }; /** Set only the year, month, date parts (keep existing time) */ Date.prototype.setDateOnly = function(date) { var tmp = new Date(date); this.setDate(1); this.setFullYear(tmp.getFullYear()); this.setMonth(tmp.getMonth()); this.setDate(tmp.getDate()); }; /** Prints the date in a string according to the given format. */ Date.prototype.print = function (str) { var m = this.getMonth(); var d = this.getDate(); var y = this.getFullYear(); var wn = this.getWeekNumber(); var w = this.getDay(); var s = {}; var hr = this.getHours(); var pm = (hr >= 12); var ir = (pm) ? (hr - 12) : hr; var dy = this.getDayOfYear(); if (ir == 0) ir = 12; var min = this.getMinutes(); var sec = this.getSeconds(); s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] s["%A"] = Calendar._DN[w]; // full weekday name s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] s["%B"] = Calendar._MN[m]; // full month name // FIXME: %c : preferred date and time representation for the current locale s["%C"] = 1 + Math.floor(y / 100); // the century number s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) s["%e"] = d; // the day of the month (range 1 to 31) // FIXME: %D : american date style: %m/%d/%y // FIXME: %E, %F, %G, %g, %h (man strftime) s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) s["%k"] = hr; // hour, range 0 to 23 (24h format) s["%l"] = ir; // hour, range 1 to 12 (12h format) s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 s["%n"] = "\n"; // a newline character s["%p"] = pm ? "PM" : "AM"; s["%P"] = pm ? "pm" : "am"; // FIXME: %r : the time in am/pm notation %I:%M:%S %p // FIXME: %R : the time in 24-hour notation %H:%M s["%s"] = Math.floor(this.getTime() / 1000); s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 s["%t"] = "\t"; // a tab character // FIXME: %T : the time in 24-hour notation (%H:%M:%S) s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) // FIXME: %x : preferred date representation for the current locale without the time // FIXME: %X : preferred time representation for the current locale without the date s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) s["%Y"] = y; // year with the century s["%%"] = "%"; // a literal '%' character var re = /%./g; if (!Calendar.is_ie5 && !Calendar.is_khtml) return str.replace(re, function (par) { return s[par] || par; }); var a = str.match(re); for (var i = 0; i < a.length; i++) { var tmp = s[a[i]]; if (tmp) { re = new RegExp(a[i], 'g'); str = str.replace(re, tmp); } } return str; }; Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; Date.prototype.setFullYear = function(y) { var d = new Date(this); d.__msh_oldSetFullYear(y); if (d.getMonth() != this.getMonth()) this.setDate(28); this.__msh_oldSetFullYear(y); }; // END: DATE OBJECT PATCHES // global object that remembers the calendar window._dynarch_popupCalendar = null; ================================================ FILE: public/javascripts/calendar/lang/calendar-bg.js ================================================ // ** I18N // Calendar BG language // Author: Nikolay Solakov, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"); // short month names Calendar._SMN = new Array ("Яну", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "За календара"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Избор на дата:\n" + "- Използвайте \xab, \xbb за избор на година\n" + "- Използвайте " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " за избор на месец\n" + "- Задръжте натиснат бутона за списък с години/месеци."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Избор на час:\n" + "- Кликнете на числата от часа за да ги увеличите\n" + "- или Shift-click за намаляването им\n" + "- или кликнете и влачете за по-бърза промяна."; Calendar._TT["PREV_YEAR"] = "Предишна година (задръжте за списък)"; Calendar._TT["PREV_MONTH"] = "Предишен месец (задръжте за списък)"; Calendar._TT["GO_TODAY"] = "Днешна дата"; Calendar._TT["NEXT_MONTH"] = "Следващ месец (задръжте за списък)"; Calendar._TT["NEXT_YEAR"] = "Следваща година (задръжте за списък)"; Calendar._TT["SEL_DATE"] = "Избор на дата"; Calendar._TT["DRAG_TO_MOVE"] = "Дръпнете за преместване"; Calendar._TT["PART_TODAY"] = " (днес)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Седмицата започва с %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Затвори"; Calendar._TT["TODAY"] = "Днес"; Calendar._TT["TIME_PART"] = "(Shift-)Click или влачене за промяна на стойност"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "седм"; Calendar._TT["TIME"] = "Час:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-bs.js ================================================ // ** I18N // Calendar BS language // Autor: Ernad Husremović // // Preuzeto od Dragan Matic, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Nedjelja", "Ponedeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota", "Nedelja"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub", "Ned"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O kalendaru"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Preth. godina (drži pritisnuto za meni)"; Calendar._TT["PREV_MONTH"] = "Preth. mjesec (drži pritisnuto za meni)"; Calendar._TT["GO_TODAY"] = "Na današnji dan"; Calendar._TT["NEXT_MONTH"] = "Naredni mjesec (drži pritisnuto za meni)"; Calendar._TT["NEXT_YEAR"] = "Naredna godina (drži prisnuto za meni)"; Calendar._TT["SEL_DATE"] = "Izbor datuma"; Calendar._TT["DRAG_TO_MOVE"] = "Prevucite za izmjenu"; Calendar._TT["PART_TODAY"] = " (danas)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Prikaži %s prvo"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Zatvori"; Calendar._TT["TODAY"] = "Danas"; Calendar._TT["TIME_PART"] = "(Shift-)Klik ili prevlačenje za izmjenu vrijednosti"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Vrijeme:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-ca.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"); // short month names Calendar._SMN = new Array ("Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Quant al calendari"; Calendar._TT["ABOUT"] = "Selector DHTML de data/hora\n" + "(c) dynarch.com 2002-2005 / Autor: Mihai Bazon\n" + // don't translate this this ;-) "Per a aconseguir l'última versió visiteu: http://www.dynarch.com/projects/calendar/\n" + "Distribuït sota la llicència GNU LGPL. Vegeu http://gnu.org/licenses/lgpl.html per a més detalls." + "\n\n" + "Selecció de la data:\n" + "- Utilitzeu els botons \xab, \xbb per a seleccionar l'any\n" + "- Utilitzeu els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per a selecciona el mes\n" + "- Mantingueu premut el botó del ratolí sobre qualsevol d'aquests botons per a uns selecció més ràpida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selecció de l'hora:\n" + "- Feu clic en qualsevol part de l'hora per a incrementar-la\n" + "- o premeu majúscules per a disminuir-la\n" + "- o feu clic i arrossegueu per a una selecció més ràpida."; Calendar._TT["PREV_YEAR"] = "Any anterior (mantenir per menú)"; Calendar._TT["PREV_MONTH"] = "Mes anterior (mantenir per menú)"; Calendar._TT["GO_TODAY"] = "Anar a avui"; Calendar._TT["NEXT_MONTH"] = "Mes següent (mantenir per menú)"; Calendar._TT["NEXT_YEAR"] = "Any següent (mantenir per menú)"; Calendar._TT["SEL_DATE"] = "Sel·lecciona data"; Calendar._TT["DRAG_TO_MOVE"] = "Arrossega per a moure"; Calendar._TT["PART_TODAY"] = " (avui)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Primer mostra el %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Tanca"; Calendar._TT["TODAY"] = "Avui"; Calendar._TT["TIME_PART"] = "(Majúscules-)Feu clic o arrossegueu per a canviar el valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; Calendar._TT["WK"] = "set"; Calendar._TT["TIME"] = "Hora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-cs.js ================================================ /* calendar-cs-win.js language: Czech encoding: windows-1250 author: Lubos Jerabek (xnet@seznam.cz) Jan Uhlir (espinosa@centrum.cz) */ // ** I18N Calendar._DN = new Array('Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota','Neděle'); Calendar._SDN = new Array('Ne','Po','Út','St','Čt','Pá','So','Ne'); Calendar._MN = new Array('Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec'); Calendar._SMN = new Array('Led','Úno','Bře','Dub','Kvě','Črv','Čvc','Srp','Zář','Říj','Lis','Pro'); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O komponentě kalendář"; Calendar._TT["TOGGLE"] = "Změna prvního dne v týdnu"; Calendar._TT["PREV_YEAR"] = "Předchozí rok (přidrž pro menu)"; Calendar._TT["PREV_MONTH"] = "Předchozí měsíc (přidrž pro menu)"; Calendar._TT["GO_TODAY"] = "Dnešní datum"; Calendar._TT["NEXT_MONTH"] = "Další měsíc (přidrž pro menu)"; Calendar._TT["NEXT_YEAR"] = "Další rok (přidrž pro menu)"; Calendar._TT["SEL_DATE"] = "Vyber datum"; Calendar._TT["DRAG_TO_MOVE"] = "Chyť a táhni, pro přesun"; Calendar._TT["PART_TODAY"] = " (dnes)"; Calendar._TT["MON_FIRST"] = "Ukaž jako první Pondělí"; //Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Výběr datumu:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Použijte tlačítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k výběru měsíce\n" + "- Podržte tlačítko myši na jakémkoliv z těch tlačítek pro rychlejší výběr."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Výběr času:\n" + "- Klikněte na jakoukoliv z částí výběru času pro zvýšení.\n" + "- nebo Shift-click pro snížení\n" + "- nebo klikněte a táhněte pro rychlejší výběr."; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Zobraz %s první"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Zavřít"; Calendar._TT["TODAY"] = "Dnes"; Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro změnu hodnoty"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Čas:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-da.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Translater: Mads N. Vestergaard // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Januar", "Februar", "Marts", "April", "Maj", "Juni", "Juli", "August", "September", "Oktober", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Om denne kalender"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For seneste version, besøg: http://www.dynarch.com/projects/calendar/\n" + "Distribueret under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detaljer." + "\n\n" + "Dato valg:\n" + "- Benyt \xab, \xbb tasterne til at vælge år\n" + "- Benyt " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " tasterne til at vælge måned\n" + "- Hold musetasten inde på punkterne for at vælge hurtigere."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Tids valg:\n" + "- Klik på en af tidsrammerne for at forhøje det\n" + "- eller Shift-klik for at mindske det\n" + "- eller klik og træk for hurtigere valg."; Calendar._TT["PREV_YEAR"] = "Forrige år (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Forrige måned (hold for menu)"; Calendar._TT["GO_TODAY"] = "Gå til dags dato"; Calendar._TT["NEXT_MONTH"] = "Næste måned (hold for menu)"; Calendar._TT["NEXT_YEAR"] = "Næste år (hold for menu)"; Calendar._TT["SEL_DATE"] = "Vælg dato"; Calendar._TT["DRAG_TO_MOVE"] = "Træk for at flytte"; Calendar._TT["PART_TODAY"] = " (dags dato)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Vis %s først"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "6,7"; Calendar._TT["CLOSE"] = "Luk"; Calendar._TT["TODAY"] = "I dag"; Calendar._TT["TIME_PART"] = "(Shift-)Klik eller træk for at ændre værdi"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "uge"; Calendar._TT["TIME"] = "Tid:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-de.js ================================================ // ** I18N // Calendar DE language // Author: Jack (tR), // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // short day names Calendar._SDN = new Array ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"); // full month names Calendar._MN = new Array ("Januar", "Februar", "M\u00e4rz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "M\u00e4r", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "\u00DCber dieses Kalendarmodul"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Datum ausw\u00e4hlen:\n" + "- Benutzen Sie die \xab, \xbb Buttons um das Jahr zu w\u00e4hlen\n" + "- Benutzen Sie die " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " Buttons um den Monat zu w\u00e4hlen\n" + "- F\u00fcr eine Schnellauswahl halten Sie die Maustaste \u00fcber diesen Buttons fest."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Zeit ausw\u00e4hlen:\n" + "- Klicken Sie auf die Teile der Uhrzeit, um diese zu erh\u00F6hen\n" + "- oder klicken Sie mit festgehaltener Shift-Taste um diese zu verringern\n" + "- oder klicken und festhalten f\u00fcr Schnellauswahl."; Calendar._TT["TOGGLE"] = "Ersten Tag der Woche w\u00e4hlen"; Calendar._TT["PREV_YEAR"] = "Voriges Jahr (Festhalten f\u00fcr Schnellauswahl)"; Calendar._TT["PREV_MONTH"] = "Voriger Monat (Festhalten f\u00fcr Schnellauswahl)"; Calendar._TT["GO_TODAY"] = "Heute ausw\u00e4hlen"; Calendar._TT["NEXT_MONTH"] = "N\u00e4chst. Monat (Festhalten f\u00fcr Schnellauswahl)"; Calendar._TT["NEXT_YEAR"] = "N\u00e4chst. Jahr (Festhalten f\u00fcr Schnellauswahl)"; Calendar._TT["SEL_DATE"] = "Datum ausw\u00e4hlen"; Calendar._TT["DRAG_TO_MOVE"] = "Zum Bewegen festhalten"; Calendar._TT["PART_TODAY"] = " (Heute)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Woche beginnt mit %s "; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Schlie\u00dfen"; Calendar._TT["TODAY"] = "Heute"; Calendar._TT["TIME_PART"] = "(Shift-)Klick oder Festhalten und Ziehen um den Wert zu \u00e4ndern"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Zeit:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-en.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "About the calendar"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; Calendar._TT["GO_TODAY"] = "Go Today"; Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; Calendar._TT["SEL_DATE"] = "Select date"; Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; Calendar._TT["PART_TODAY"] = " (today)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Display %s first"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Close"; Calendar._TT["TODAY"] = "Today"; Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Time:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-es.js ================================================ // ** I18N // Calendar ES (spanish) language // Author: Mihai Bazon, // Updater: Servilio Afre Puentes // Updated: 2004-06-03 // Encoding: utf-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"); // short month names Calendar._SMN = new Array ("Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Acerca del calendario"; Calendar._TT["ABOUT"] = "Selector DHTML de Fecha/Hora\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Para conseguir la última versión visite: http://www.dynarch.com/projects/calendar/\n" + "Distribuido bajo licencia GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para más detalles." + "\n\n" + "Selección de fecha:\n" + "- Use los botones \xab, \xbb para seleccionar el año\n" + "- Use los botones " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" + "- Mantenga pulsado el ratón en cualquiera de estos botones para una selección rápida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selección de hora:\n" + "- Pulse en cualquiera de las partes de la hora para incrementarla\n" + "- o pulse las mayúsculas mientras hace clic para decrementarla\n" + "- o haga clic y arrastre el ratón para una selección más rápida."; Calendar._TT["PREV_YEAR"] = "Año anterior (mantener para menú)"; Calendar._TT["PREV_MONTH"] = "Mes anterior (mantener para menú)"; Calendar._TT["GO_TODAY"] = "Ir a hoy"; Calendar._TT["NEXT_MONTH"] = "Mes siguiente (mantener para menú)"; Calendar._TT["NEXT_YEAR"] = "Año siguiente (mantener para menú)"; Calendar._TT["SEL_DATE"] = "Seleccionar fecha"; Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover"; Calendar._TT["PART_TODAY"] = " (hoy)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Hacer %s primer día de la semana"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Cerrar"; Calendar._TT["TODAY"] = "Hoy"; Calendar._TT["TIME_PART"] = "(Mayúscula-)Clic o arrastre para cambiar valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; Calendar._TT["WK"] = "sem"; Calendar._TT["TIME"] = "Hora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-fi.js ================================================ // ** I18N // Calendar FI language // Author: Antti Perkiömäki // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Sunnuntai", "Maanantai", "Tiistai", "Keskiviikko", "Torstai", "Perjantai", "Lauantai", "Sunnuntai"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Su", "Ma", "Ti", "Ke", "To", "Pe", "La", "Su"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Tammikuu", "Helmikuu", "Maaliskuu", "Huhtikuu", "Toukokuu", "Kesäkuu", "Heinäkuu", "Elokuu", "Syyskuu", "Lokakuu", "Marraskuu", "Joulukuu"); // short month names Calendar._SMN = new Array ("Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", "Heinä", "Elo", "Syys", "Loka", "Marras", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Tietoa kalenterista"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Tekijä: Mihai Bazon\n" + // don't translate this this ;-) "Viimeisin versio: http://www.dynarch.com/projects/calendar/\n" + "Jaettu GNU LGPL alaisena. Katso lisätiedot http://gnu.org/licenses/lgpl.html" + "\n\n" + "Päivä valitsin:\n" + "- Käytä \xab, \xbb painikkeita valitaksesi vuoden\n" + "- Käytä " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " painikkeita valitaksesi kuukauden\n" + "- Pidä alhaalla hiiren painiketta missä tahansa yllämainituissa painikkeissa valitaksesi nopeammin."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Ajan valinta:\n" + "- Paina mitä tahansa ajan osaa kasvattaaksesi sitä\n" + "- tai Vaihtonäppäin-paina laskeaksesi sitä\n" + "- tai paina ja raahaa valitaksesi nopeammin."; Calendar._TT["PREV_YEAR"] = "Edellinen vuosi (valikko tulee painaessa)"; Calendar._TT["PREV_MONTH"] = "Edellinen kuukausi (valikko tulee painaessa)"; Calendar._TT["GO_TODAY"] = "Siirry Tänään"; Calendar._TT["NEXT_MONTH"] = "Seuraava kuukausi (valikko tulee painaessa)"; Calendar._TT["NEXT_YEAR"] = "Seuraava vuosi (valikko tulee painaessa)"; Calendar._TT["SEL_DATE"] = "Valitse päivä"; Calendar._TT["DRAG_TO_MOVE"] = "Rahaa siirtääksesi"; Calendar._TT["PART_TODAY"] = " (tänään)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Näytä %s ensin"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "6,0"; Calendar._TT["CLOSE"] = "Sulje"; Calendar._TT["TODAY"] = "Tänään"; Calendar._TT["TIME_PART"] = "(Vaihtonäppäin-)Paina tai raahaa vaihtaaksesi arvoa"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "vko"; Calendar._TT["TIME"] = "Aika:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-fr.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // Translator: David Duret, from previous french version // full day names Calendar._DN = new Array ("Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"); // short month names Calendar._SMN = new Array ("Jan", "Fev", "Mar", "Avr", "Mai", "Juin", "Juil", "Aout", "Sep", "Oct", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "A propos du calendrier"; Calendar._TT["ABOUT"] = "DHTML Date/Heure Selecteur\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Pour la derniere version visitez : http://www.dynarch.com/projects/calendar/\n" + "Distribué par GNU LGPL. Voir http://gnu.org/licenses/lgpl.html pour les details." + "\n\n" + "Selection de la date :\n" + "- Utiliser les bouttons \xab, \xbb pour selectionner l\'annee\n" + "- Utiliser les bouttons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pour selectionner les mois\n" + "- Garder la souris sur n'importe quels boutons pour une selection plus rapide"; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selection de l\'heure :\n" + "- Cliquer sur heures ou minutes pour incrementer\n" + "- ou Maj-clic pour decrementer\n" + "- ou clic et glisser-deplacer pour une selection plus rapide"; Calendar._TT["PREV_YEAR"] = "Année préc. (maintenir pour menu)"; Calendar._TT["PREV_MONTH"] = "Mois préc. (maintenir pour menu)"; Calendar._TT["GO_TODAY"] = "Atteindre la date du jour"; Calendar._TT["NEXT_MONTH"] = "Mois suiv. (maintenir pour menu)"; Calendar._TT["NEXT_YEAR"] = "Année suiv. (maintenir pour menu)"; Calendar._TT["SEL_DATE"] = "Sélectionner une date"; Calendar._TT["DRAG_TO_MOVE"] = "Déplacer"; Calendar._TT["PART_TODAY"] = " (Aujourd'hui)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Afficher %s en premier"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Fermer"; Calendar._TT["TODAY"] = "Aujourd'hui"; Calendar._TT["TIME_PART"] = "(Maj-)Clic ou glisser pour modifier la valeur"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "Sem."; Calendar._TT["TIME"] = "Heure :"; ================================================ FILE: public/javascripts/calendar/lang/calendar-gl.js ================================================ // ** I18N // Calendar GL (galician) language // Author: Martín Vázquez Cabanas, // Updated: 2009-01-23 // Encoding: utf-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Domingo", "Luns", "Martes", "Mércores", "Xoves", "Venres", "Sábado", "Domingo"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dom", "Lun", "Mar", "Mér", "Xov", "Ven", "Sáb", "Dom"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Xaneiro", "Febreiro", "Marzo", "Abril", "Maio", "Xuño", "Xullo", "Agosto", "Setembro", "Outubro", "Novembro", "Decembro"); // short month names Calendar._SMN = new Array ("Xan", "Feb", "Mar", "Abr", "Mai", "Xun", "Xull", "Ago", "Set", "Out", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Acerca do calendario"; Calendar._TT["ABOUT"] = "Selector DHTML de Data/Hora\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Para conseguila última versión visite: http://www.dynarch.com/projects/calendar/\n" + "Distribuído baixo licenza GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para máis detalles." + "\n\n" + "Selección de data:\n" + "- Use os botóns \xab, \xbb para seleccionalo ano\n" + "- Use os botóns " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionalo mes\n" + "- Manteña pulsado o rato en calquera destes botóns para unha selección rápida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selección de hora:\n" + "- Pulse en calquera das partes da hora para incrementala\n" + "- ou pulse maiúsculas mentres fai clic para decrementala\n" + "- ou faga clic e arrastre o rato para unha selección máis rápida."; Calendar._TT["PREV_YEAR"] = "Ano anterior (manter para menú)"; Calendar._TT["PREV_MONTH"] = "Mes anterior (manter para menú)"; Calendar._TT["GO_TODAY"] = "Ir a hoxe"; Calendar._TT["NEXT_MONTH"] = "Mes seguinte (manter para menú)"; Calendar._TT["NEXT_YEAR"] = "Ano seguinte (manter para menú)"; Calendar._TT["SEL_DATE"] = "Seleccionar data"; Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover"; Calendar._TT["PART_TODAY"] = " (hoxe)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Facer %s primeiro día da semana"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Pechar"; Calendar._TT["TODAY"] = "Hoxe"; Calendar._TT["TIME_PART"] = "(Maiúscula-)Clic ou arrastre para cambiar valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; Calendar._TT["WK"] = "sem"; Calendar._TT["TIME"] = "Hora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-he.js ================================================ // ** I18N // Calendar HE language // Author: Saggi Mizrahi // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("א", "ב", "ג", "ד", "ה", "ו", "ש", "א"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"); // short month names Calendar._SMN = new Array ("ינו'", "פבו'", "מרץ", "אפר'", "מאי", "יונ'", "יול'", "אוג'", "ספט'", "אוקט'", "נוב'", "דצמ'"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "אודות לוח השנה"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "שנה קודמת (החזק לתפריט)"; Calendar._TT["PREV_MONTH"] = "חודש קודם (החזק לתפריט)"; Calendar._TT["GO_TODAY"] = "לך להיום"; Calendar._TT["NEXT_MONTH"] = "חודש הבא (החזק לתפריט)"; Calendar._TT["NEXT_YEAR"] = "שנה הבאה (החזק לתפריט)"; Calendar._TT["SEL_DATE"] = "בחר תאריך"; Calendar._TT["DRAG_TO_MOVE"] = "משוך כדי להזיז"; Calendar._TT["PART_TODAY"] = " (היום)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "הצג %s קודם"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "5,6"; Calendar._TT["CLOSE"] = "סגור"; Calendar._TT["TODAY"] = "היום"; Calendar._TT["TIME_PART"] = "(Shift-)לחץ או משוך כדי לשנות את הערך"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "זמן:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-hu.js ================================================ // ** I18N // Calendar HU language // Author: Takács Gábor // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat", "Vasárnap"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Vas", "Hét", "Ked", "Sze", "Csü", "Pén", "Szo", "Vas"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Szep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "A naptár leírása"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Előző év (nyomvatart = menü)"; Calendar._TT["PREV_MONTH"] = "Előző hónap (nyomvatart = menü)"; Calendar._TT["GO_TODAY"] = "Irány a Ma"; Calendar._TT["NEXT_MONTH"] = "Következő hónap (nyomvatart = menü)"; Calendar._TT["NEXT_YEAR"] = "Következő év (nyomvatart = menü)"; Calendar._TT["SEL_DATE"] = "Válasszon dátumot"; Calendar._TT["DRAG_TO_MOVE"] = "Fogd és vidd"; Calendar._TT["PART_TODAY"] = " (ma)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "%s megjelenítése elsőként"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Bezár"; Calendar._TT["TODAY"] = "Ma"; Calendar._TT["TIME_PART"] = "(Shift-)Click vagy húzd az érték változtatásához"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y.%m.%d"; Calendar._TT["TT_DATE_FORMAT"] = "%B %e, %A"; Calendar._TT["WK"] = "hét"; Calendar._TT["TIME"] = "Idő:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-id.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // Translator: Raden Prabowo, // full day names Calendar._DN = new Array ("Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Ming", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Ming"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Mengenai kalender"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Versi terbaru terdapat di: http://www.dynarch.com/projects/calendar/\n" + "Disebarkan dibawah lisensi GNU LGPL. Lihat http://gnu.org/licenses/lgpl.html untuk detil." + "\n\n" + "Pemilihan tanggal:\n" + "- Gunakan tombol \xab, \xbb untuk memilih tahun\n" + "- Gunakan tombol " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " untuk memilih bulan\n" + "- Tekan terus tombol kanan pada mouse atau salah satu tombol diatas untuk memilih lebih cepat."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Pemilihan waktu:\n" + "- Klik bagian waktu untuk menaikkan\n" + "- atau Shift-klick untuk menurunkan\n" + "- atau klik dan geser untuk pemilihan yang lebih cepat."; Calendar._TT["PREV_YEAR"] = "Tahun sebelumnya. (tekan terus untuk menu)"; Calendar._TT["PREV_MONTH"] = "Bulan sebelumnya. (tekan terus untuk menu)"; Calendar._TT["GO_TODAY"] = "Ke Hari ini"; Calendar._TT["NEXT_MONTH"] = "Bulan berikutnya. (tekan terus untuk menu)"; Calendar._TT["NEXT_YEAR"] = "Tahun berikutnya. (tekan terus untuk menu)"; Calendar._TT["SEL_DATE"] = "Pilih tanggal"; Calendar._TT["DRAG_TO_MOVE"] = "Geser untuk menggerakkan"; Calendar._TT["PART_TODAY"] = " (hari ini)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Tampilkan %s lebih dulu"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Tutup"; Calendar._TT["TODAY"] = "Hari ini"; Calendar._TT["TIME_PART"] = "(Shift-)Click atau geser untuk mengubah nilai"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; //Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; Calendar._TT["WK"] = "mg"; Calendar._TT["TIME"] = "Waktu:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-it.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato", "Domenica"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"); // short month names Calendar._SMN = new Array ("Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Informazioni sul calendario"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Anno prec. (tieni premuto per menu)"; Calendar._TT["PREV_MONTH"] = "Mese prec. (tieni premuto per menu)"; Calendar._TT["GO_TODAY"] = "Oggi"; Calendar._TT["NEXT_MONTH"] = "Mese succ. (tieni premuto per menu)"; Calendar._TT["NEXT_YEAR"] = "Anno succ. (tieni premuto per menu)"; Calendar._TT["SEL_DATE"] = "Seleziona data"; Calendar._TT["DRAG_TO_MOVE"] = "Trascina per spostare"; Calendar._TT["PART_TODAY"] = " (oggi)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Mostra %s per primo"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Chiudi"; Calendar._TT["TODAY"] = "Oggi"; Calendar._TT["TIME_PART"] = "(Shift-)Click o trascina per modificare"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "sett"; Calendar._TT["TIME"] = "Ora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-ja.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("日", "月", "火", "水", "木", "金", "土"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); // short month names Calendar._SMN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "このカレンダーについて"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "日付の選択方法:\n" + "- \xab, \xbb ボタンで年を選択。\n" + "- " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ボタンで年を選択。\n" + "- 上記ボタンの長押しでメニューから選択。"; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "前年 (長押しでメニュー表示)"; Calendar._TT["PREV_MONTH"] = "前月 (長押しでメニュー表示)"; Calendar._TT["GO_TODAY"] = "今日の日付を選択"; Calendar._TT["NEXT_MONTH"] = "翌月 (長押しでメニュー表示)"; Calendar._TT["NEXT_YEAR"] = "翌年 (長押しでメニュー表示)"; Calendar._TT["SEL_DATE"] = "日付を選択してください"; Calendar._TT["DRAG_TO_MOVE"] = "ドラッグで移動"; Calendar._TT["PART_TODAY"] = " (今日)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "%s始まりで表示"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "閉じる"; Calendar._TT["TODAY"] = "今日"; Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%b%e日(%a)"; Calendar._TT["WK"] = "週"; Calendar._TT["TIME"] = "Time:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-ko.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("일", "월", "화", "수", "목", "금", "토", "일"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"); // short month names Calendar._SMN = new Array ("1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "이 달력은 ... & 도움말"; Calendar._TT["ABOUT"] = "DHTML 날짜/시간 선택기\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "최신 버전을 구하려면 여기로: http://www.dynarch.com/projects/calendar/\n" + "배포라이센스:GNU LGPL. 참조:http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "날짜 선택:\n" + "- 해를 선택하려면 \xab, \xbb 버튼을 사용하세요.\n" + "- 달을 선택하려면 " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 버튼을 사용하세요.\n" + "- 좀 더 빠르게 선택하려면 위의 버튼을 꾹 눌러주세요."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "시간 선택:\n" + "- 시, 분을 더하려면 클릭하세요.\n" + "- 시, 분을 빼려면 쉬프트 누르고 클릭하세요.\n" + "- 좀 더 빠르게 선택하려면 클릭하고 드래그하세요."; Calendar._TT["PREV_YEAR"] = "이전 해"; Calendar._TT["PREV_MONTH"] = "이전 달"; Calendar._TT["GO_TODAY"] = "오늘로 이동"; Calendar._TT["NEXT_MONTH"] = "다음 달"; Calendar._TT["NEXT_YEAR"] = "다음 해"; Calendar._TT["SEL_DATE"] = "날짜 선택"; Calendar._TT["DRAG_TO_MOVE"] = "이동(드래그)"; Calendar._TT["PART_TODAY"] = " (오늘)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "[%s]을 처음으로"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "닫기"; Calendar._TT["TODAY"] = "오늘"; Calendar._TT["TIME_PART"] = "클릭(+),쉬프트+클릭(-),드래그"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "주"; Calendar._TT["TIME"] = "시간:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-lt.js ================================================ // ** I18N // Calendar LT language // Author: Gediminas Muižis, // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // Ver: 0.2 // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Sekmadienis", "Pirmadienis", "Antradienis", "Trečiadienis", "Ketvirtadienis", "Penktadienis", "Šeštadienis", "Sekmadienis"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Sek", "Pir", "Ant", "Tre", "Ket", "Pen", "Šeš", "Sek"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", "Liepa", "Rudpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis"); // short month names Calendar._SMN = new Array ("Sau", "Vas", "Kov", "Bal", "Geg", "Brž", "Lie", "Rgp", "Rgs", "Spl", "Lap", "Grd"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Apie kalendorių"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Datos pasirinkimas:\n" + "- Naudoti \xab, \xbb mygtukus norint pasirinkti metus\n" + "- Naudoti " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " mygtukus norint pasirinkti mėnesį\n" + "- PAlaikykite nuspaudę bet kurį nygtuką norėdami iškviesti greitąjį meniu."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Datos pasirinkimas:\n" + "- Paspaudus ant valandos ar minutės, jų reikšmės padidėja\n" + "- arba Shift-paspaudimas norint sumažinti reikšmę\n" + "- arba paspauskite ir tempkite norint greičiau keisti reikšmę."; Calendar._TT["PREV_YEAR"] = "Ankst. metai (laikyti, norint iškviesti meniu)"; Calendar._TT["PREV_MONTH"] = "Ankst. mėnuo (laikyti, norint iškviesti meniu)"; Calendar._TT["GO_TODAY"] = "Šiandien"; Calendar._TT["NEXT_MONTH"] = "Kitas mėnuo (laikyti, norint iškviesti meniu)"; Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikyti, norint iškviesti meniu)"; Calendar._TT["SEL_DATE"] = "Pasirinkti datą"; Calendar._TT["DRAG_TO_MOVE"] = "Perkelkite pėlyte"; Calendar._TT["PART_TODAY"] = " (šiandien)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Rodyti %s pirmiau"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Uždaryti"; Calendar._TT["TODAY"] = "Šiandien"; Calendar._TT["TIME_PART"] = "(Shift-)Spausti ar tempti, norint pakeisti reikšmę"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "sav"; Calendar._TT["TIME"] = "Laikas:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-mk.js ================================================ // ** I18N // Calendar МК language // Author: Илин Татабитовски, // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("недела", "понеделник", "вторник", "среда", "четврток", "петок", "сабота", "недела"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("нед", "пон", "вто", "сре", "чет", "пет", "саб", "нед"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("јануари", "февруари", "март", "април", "мај", "јуни", "јули", "август", "септември", "октомври", "ноември", "декември"); // short month names Calendar._SMN = new Array ("јан", "фев", "мар", "апр", "мај", "јун", "јул", "авг", "сеп", "окт", "ное", "дек"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "За календарот"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Претходна година (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Претходен месец (hold for menu)"; Calendar._TT["GO_TODAY"] = "Go Today"; Calendar._TT["NEXT_MONTH"] = "Следен месец (hold for menu)"; Calendar._TT["NEXT_YEAR"] = "Следна година (hold for menu)"; Calendar._TT["SEL_DATE"] = "Изберете дата"; Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; Calendar._TT["PART_TODAY"] = " (денес)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Прикажи %s прво"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Затвори"; Calendar._TT["TODAY"] = "Денес"; Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; Calendar._TT["WK"] = "нед"; Calendar._TT["TIME"] = "Време:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-nl.js ================================================ // ** I18N // Calendar NL language // Author: Linda van den Brink, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Maa", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Over de kalender"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Datum selectie:\n" + "- Gebruik de \xab, \xbb knoppen om het jaar te selecteren\n" + "- Gebruik de " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knoppen om de maand te selecteren\n" + "- Houd de muisknop ingedrukt op een van de knoppen voor snellere selectie."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Tijd selectie:\n" + "- Klik op een deel van de tijd om het te verhogen\n" + "- of Shift-click om het te verlagen\n" + "- of klik en sleep voor snellere selectie."; Calendar._TT["PREV_YEAR"] = "Vorig jaar (vasthouden voor menu)"; Calendar._TT["PREV_MONTH"] = "Vorige maand (vasthouden voor menu)"; Calendar._TT["GO_TODAY"] = "Ga naar vandaag"; Calendar._TT["NEXT_MONTH"] = "Volgende maand (vasthouden voor menu)"; Calendar._TT["NEXT_YEAR"] = "Volgend jaar(vasthouden voor menu)"; Calendar._TT["SEL_DATE"] = "Selecteer datum"; Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te verplaatsen"; Calendar._TT["PART_TODAY"] = " (vandaag)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Toon %s eerst"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Sluiten"; Calendar._TT["TODAY"] = "Vandaag"; Calendar._TT["TIME_PART"] = "(Shift-)klik of sleep om waarde te wijzigen"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Tijd:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-no.js ================================================ // ** I18N // Calendar NO language (Norwegian/Norsk bokmål) // Author: Kai Olav Fredriksen // full day names Calendar._DN = new Array ("Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"); Calendar._SDN_len = 3; // short day name length Calendar._SMN_len = 3; // short month name length // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Januar", "Februar", "Mars", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Desember"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Om kalenderen"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Forrige år (hold for meny)"; Calendar._TT["PREV_MONTH"] = "Forrige måned (hold for meny)"; Calendar._TT["GO_TODAY"] = "Gå til idag"; Calendar._TT["NEXT_MONTH"] = "Neste måned (hold for meny)"; Calendar._TT["NEXT_YEAR"] = "Neste år (hold for meny)"; Calendar._TT["SEL_DATE"] = "Velg dato"; Calendar._TT["DRAG_TO_MOVE"] = "Dra for å flytte"; Calendar._TT["PART_TODAY"] = " (idag)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Vis %s først"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Lukk"; Calendar._TT["TODAY"] = "Idag"; Calendar._TT["TIME_PART"] = "(Shift-)Klikk eller dra for å endre verdi"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%%d.%m.%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "uke"; Calendar._TT["TIME"] = "Tid:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-pl.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota", "Niedziela"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Nie", "Pon", "Wto", "Śro", "Czw", "Pią", "Sob", "Nie"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"); // short month names Calendar._SMN = new Array ("Sty", "Lut", "Mar", "Kwi", "Maj", "Cze", "Lip", "Sie", "Wrz", "Paź", "Lis", "Gru"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O kalendarzu"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Po ostatnią wersję odwiedź: http://www.dynarch.com/projects/calendar/\n" + "Rozpowszechniany pod licencją GNU LGPL. Zobacz: http://gnu.org/licenses/lgpl.html z celu zapoznania się ze szczegółami." + "\n\n" + "Wybór daty:\n" + "- Użyj \xab, \xbb przycisków by zaznaczyć rok\n" + "- Użyj " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " przycisków by zaznaczyć miesiąc\n" + "- Trzymaj wciśnięty przycisk myszy na każdym z powyższych przycisków by przyśpieszyć zaznaczanie."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Wybór czasu:\n" + "- Kliknij na każdym przedziale czasu aby go powiększyć\n" + "- lub kliknij z przyciskiem Shift by go zmniejszyć\n" + "- lub kliknij i przeciągnij dla szybszego zaznaczenia."; Calendar._TT["PREV_YEAR"] = "Poprz. rok (przytrzymaj dla menu)"; Calendar._TT["PREV_MONTH"] = "Poprz. miesiąc (przytrzymaj dla menu)"; Calendar._TT["GO_TODAY"] = "Idź do Dzisiaj"; Calendar._TT["NEXT_MONTH"] = "Następny miesiąc(przytrzymaj dla menu)"; Calendar._TT["NEXT_YEAR"] = "Następny rok (przytrzymaj dla menu)"; Calendar._TT["SEL_DATE"] = "Zaznacz datę"; Calendar._TT["DRAG_TO_MOVE"] = "Przeciągnij by przenieść"; Calendar._TT["PART_TODAY"] = " (dzisiaj)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Pokaż %s pierwszy"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Zamknij"; Calendar._TT["TODAY"] = "Dzisiaj"; Calendar._TT["TIME_PART"] = "(Shift-)Kliknij lub upuść by zmienić wartość"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%R-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Czas:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-pt-br.js ================================================ // ** I18N // Calendar pt_BR language // Author: Adalberto Machado, // Review: Alexandre da Silva, // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sabado", "Domingo"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab", "Dom"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"); // short month names Calendar._SMN = new Array ("Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Sobre o calendário"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Última versão visite: http://www.dynarch.com/projects/calendar/\n" + "Distribuído sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + "\n\n" + "Seleção de data:\n" + "- Use os botões \xab, \xbb para selecionar o ano\n" + "- Use os botões " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mês\n" + "- Segure o botão do mouse em qualquer um desses botões para seleção rápida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Seleção de hora:\n" + "- Clique em qualquer parte da hora para incrementar\n" + "- ou Shift-click para decrementar\n" + "- ou clique e segure para seleção rápida."; Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)"; Calendar._TT["PREV_MONTH"] = "Ant. mês (segure para menu)"; Calendar._TT["GO_TODAY"] = "Hoje"; Calendar._TT["NEXT_MONTH"] = "Próx. mes (segure para menu)"; Calendar._TT["NEXT_YEAR"] = "Próx. ano (segure para menu)"; Calendar._TT["SEL_DATE"] = "Selecione a data"; Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; Calendar._TT["PART_TODAY"] = " (hoje)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Mostre %s primeiro"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Fechar"; Calendar._TT["TODAY"] = "Hoje"; Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; Calendar._TT["WK"] = "sm"; Calendar._TT["TIME"] = "Hora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-pt.js ================================================ // ** I18N // Calendar pt language // Author: Adalberto Machado, // Corrected by: Pedro Araújo // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"); // short month names Calendar._SMN = new Array ("Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Sobre o calendário"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "Última versão visite: http://www.dynarch.com/projects/calendar/\n" + "Distribuído sobre a licença GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + "\n\n" + "Selecção de data:\n" + "- Use os botões \xab, \xbb para seleccionar o ano\n" + "- Use os botões " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar o mês\n" + "- Segure o botão do rato em qualquer um desses botões para selecção rápida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selecção de hora:\n" + "- Clique em qualquer parte da hora para incrementar\n" + "- ou Shift-click para decrementar\n" + "- ou clique e segure para selecção rápida."; Calendar._TT["PREV_YEAR"] = "Ano ant. (segure para menu)"; Calendar._TT["PREV_MONTH"] = "Mês ant. (segure para menu)"; Calendar._TT["GO_TODAY"] = "Hoje"; Calendar._TT["NEXT_MONTH"] = "Prox. mês (segure para menu)"; Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)"; Calendar._TT["SEL_DATE"] = "Seleccione a data"; Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; Calendar._TT["PART_TODAY"] = " (hoje)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Mostre %s primeiro"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Fechar"; Calendar._TT["TODAY"] = "Hoje"; Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; Calendar._TT["WK"] = "sm"; Calendar._TT["TIME"] = "Hora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-ro.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Duminică", "Luni", "Marți", "Miercuri", "Joi", "Vineri", "Sâmbătă", "Duminică"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"); // short month names Calendar._SMN = new Array ("Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Noi", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Despre calendar"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Selectare data:\n" + "- Folositi butoanele \xab, \xbb pentru a selecta anul\n" + "- Folositi butoanele " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pentru a selecta luna\n" + "- Lasati apasat butonul pentru o selectie mai rapida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selectare timp:\n" + "- Click pe campul de timp pentru a majora timpul\n" + "- sau Shift-Click pentru a micsora\n" + "- sau click si drag pentru manipulare rapida."; Calendar._TT["PREV_YEAR"] = "Anul precedent (apasati pentru meniu)"; Calendar._TT["PREV_MONTH"] = "Luna precedenta (apasati pentru meniu)"; Calendar._TT["GO_TODAY"] = "Astazi"; Calendar._TT["NEXT_MONTH"] = "Luna viitoare (apasati pentru meniu)"; Calendar._TT["NEXT_YEAR"] = "Anul viitor (apasati pentru meniu)"; Calendar._TT["SEL_DATE"] = "Selecteaza data"; Calendar._TT["DRAG_TO_MOVE"] = "Drag pentru a muta"; Calendar._TT["PART_TODAY"] = " (azi)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Vizualizează %s prima"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Închide"; Calendar._TT["TODAY"] = "Azi"; Calendar._TT["TIME_PART"] = "(Shift-)Click sau drag pentru a schimba valoarea"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%A-%l-%z"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "săpt"; Calendar._TT["TIME"] = "Ora:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-ru.js ================================================ // ** I18N // Calendar RU language // Translation: Sly Golovanov, http://golovanov.net, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("вск", "пон", "втр", "срд", "чет", "пят", "суб", "вск"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"); // short month names Calendar._SMN = new Array ("янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "О календаре..."; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Как выбрать дату:\n" + "- При помощи кнопок \xab, \xbb можно выбрать год\n" + "- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать месяц\n" + "- Подержите эти кнопки нажатыми, чтобы появилось меню быстрого выбора."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Как выбрать время:\n" + "- При клике на часах или минутах они увеличиваются\n" + "- при клике с нажатой клавишей Shift они уменьшаются\n" + "- если нажать и двигать мышкой влево/вправо, они будут меняться быстрее."; Calendar._TT["PREV_YEAR"] = "На год назад (удерживать для меню)"; Calendar._TT["PREV_MONTH"] = "На месяц назад (удерживать для меню)"; Calendar._TT["GO_TODAY"] = "Сегодня"; Calendar._TT["NEXT_MONTH"] = "На месяц вперед (удерживать для меню)"; Calendar._TT["NEXT_YEAR"] = "На год вперед (удерживать для меню)"; Calendar._TT["SEL_DATE"] = "Выберите дату"; Calendar._TT["DRAG_TO_MOVE"] = "Перетаскивайте мышкой"; Calendar._TT["PART_TODAY"] = " (сегодня)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Первый день недели будет %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Закрыть"; Calendar._TT["TODAY"] = "Сегодня"; Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; Calendar._TT["WK"] = "нед"; Calendar._TT["TIME"] = "Время:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-sk.js ================================================ /* calendar-sk.js language: Slovak encoding: UTF-8 author: Stanislav Pach (stano.pach@seznam.cz) */ // ** I18N Calendar._DN = new Array('Nedeľa','Pondelok','Utorok','Streda','Štvrtok','Piatok','Sobota','Nedeľa'); Calendar._SDN = new Array('Ne','Po','Ut','St','Št','Pi','So','Ne'); Calendar._MN = new Array('Január','Február','Marec','Apríl','Máj','Jún','Júl','August','September','Október','November','December'); Calendar._SMN = new Array('Jan','Feb','Mar','Apr','Máj','Jún','Júl','Aug','Sep','Okt','Nov','Dec'); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O komponente kalendár"; Calendar._TT["TOGGLE"] = "Zmena prvého dňa v týždni"; Calendar._TT["PREV_YEAR"] = "Predchádzajúci rok (pridrž pre menu)"; Calendar._TT["PREV_MONTH"] = "Predchádzajúci mesiac (pridrž pre menu)"; Calendar._TT["GO_TODAY"] = "Dnešný dátum"; Calendar._TT["NEXT_MONTH"] = "Ďalší mesiac (pridrž pre menu)"; Calendar._TT["NEXT_YEAR"] = "Ďalší rok (pridrž pre menu)"; Calendar._TT["SEL_DATE"] = "Zvoľ dátum"; Calendar._TT["DRAG_TO_MOVE"] = "Chyť a ťahaj pre presun"; Calendar._TT["PART_TODAY"] = " (dnes)"; Calendar._TT["MON_FIRST"] = "Ukáž ako prvný Pondelok"; //Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Výber dátumu:\n" + "- Použijte tlačítka \xab, \xbb pre voľbu roku\n" + "- Použijte tlačítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pre výber mesiaca\n" + "- Podržte tlačítko myši na akomkoľvek z týchto tlačítok pre rýchlejší výber."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Výber času:\n" + "- Kliknite na akúkoľvek časť z výberu času pre zvýšenie.\n" + "- alebo Shift-klick pre zníženie\n" + "- alebo kliknite a ťahajte pre rýchlejší výber."; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Zobraz %s ako prvý"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Zavrieť"; Calendar._TT["TODAY"] = "Dnes"; Calendar._TT["TIME_PART"] = "(Shift-)Klikni alebo ťahaj pre zmenu hodnoty"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "týž"; Calendar._TT["TIME"] = "Čas:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-sl.js ================================================ // ** I18N // Calendar SL language // Author: Jernej Vidmar, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Nedelja", "Ponedeljek", "Torek", "Sreda", "Četrtek", "Petek", "Sobota", "Nedelja"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob", "Ned"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Januar", "Februar", "Marec", "April", "Maj", "Junij", "Julij", "Avgust", "September", "Oktober", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O koledarju"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Izbira datuma:\n" + "- Uporabite \xab, \xbb gumbe za izbiro leta\n" + "- Uporabite " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gumbe za izbiro meseca\n" + "- Za hitrejšo izbiro držite miškin gumb nad enim od zgornjih gumbov."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Izbira časa:\n" + "- Kliknite na katerikoli del časa da ga povečate\n" + "- oziroma kliknite s Shiftom za znižanje\n" + "- ali kliknite in vlecite za hitrejšo izbiro."; Calendar._TT["PREV_YEAR"] = "Prejšnje leto (držite za meni)"; Calendar._TT["PREV_MONTH"] = "Prejšnji mesec (držite za meni)"; Calendar._TT["GO_TODAY"] = "Pojdi na danes"; Calendar._TT["NEXT_MONTH"] = "Naslednji mesec (držite za meni)"; Calendar._TT["NEXT_YEAR"] = "Naslednje leto (držite za meni)"; Calendar._TT["SEL_DATE"] = "Izberite datum"; Calendar._TT["DRAG_TO_MOVE"] = "Povlecite za premik"; Calendar._TT["PART_TODAY"] = " (danes)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Najprej prikaži %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Zapri"; Calendar._TT["TODAY"] = "Danes"; Calendar._TT["TIME_PART"] = "(Shift-)klik ali povleči, da spremeniš vrednost"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Time:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-sr.js ================================================ // ** I18N // Calendar SR language // Author: Dragan Matic, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Nedelja", "Ponedeljak", "Utorak", "Sreda", "Četvrtak", "Petak", "Subota", "Nedelja"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub", "Ned"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "O kalendaru"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Preth. godina (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Preth. mesec (hold for menu)"; Calendar._TT["GO_TODAY"] = "Na današnji dan"; Calendar._TT["NEXT_MONTH"] = "Naredni mesec (hold for menu)"; Calendar._TT["NEXT_YEAR"] = "Naredna godina (hold for menu)"; Calendar._TT["SEL_DATE"] = "Izbor datuma"; Calendar._TT["DRAG_TO_MOVE"] = "Prevucite za izmenu"; Calendar._TT["PART_TODAY"] = " (danas)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Prikazi %s prvo"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Close"; Calendar._TT["TODAY"] = "Danas"; Calendar._TT["TIME_PART"] = "(Shift-)Klik ili prevlačenje za izmenu vrednosti"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Vreme:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-sv.js ================================================ // ** I18N // full day names Calendar._DN = new Array ("Söndag", "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"); Calendar._SDN_len = 3; // short day name length Calendar._SMN_len = 3; // short month name length // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Om kalendern"; Calendar._TT["ABOUT"] = "DHTML Datum/Tid-väljare\n" + "(c) dynarch.com 2002-2005 / Upphovsman: Mihai Bazon\n" + // don't translate this this ;-) "För senaste version besök: http://www.dynarch.com/projects/calendar/\n" + "Distribueras under GNU LGPL. Se http://gnu.org/licenses/lgpl.html för detaljer." + "\n\n" + "Välja datum:\n" + "- Använd \xab, \xbb knapparna för att välja år\n" + "- Använd " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knapparna för att välja månad\n" + "- Håll nere musknappen på någon av ovanstående knappar för att se snabbval."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Välja tid:\n" + "- Klicka på något av tidsfälten för att öka\n" + "- eller Skift-klicka för att minska\n" + "- eller klicka och dra för att välja snabbare."; Calendar._TT["PREV_YEAR"] = "Föreg. år (håll nere för lista)"; Calendar._TT["PREV_MONTH"] = "Föreg. månad (håll nere för lista)"; Calendar._TT["GO_TODAY"] = "Gå till Idag"; Calendar._TT["NEXT_MONTH"] = "Nästa månad (håll nere för lista)"; Calendar._TT["NEXT_YEAR"] = "Nästa år (håll nere för lista)"; Calendar._TT["SEL_DATE"] = "Välj datum"; Calendar._TT["DRAG_TO_MOVE"] = "Dra för att flytta"; Calendar._TT["PART_TODAY"] = " (idag)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Visa %s först"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Stäng"; Calendar._TT["TODAY"] = "Idag"; Calendar._TT["TIME_PART"] = "(Skift-)klicka eller dra för att ändra värde"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "v."; Calendar._TT["TIME"] = "Tid:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-th.js ================================================ // ** I18N // Calendar EN language // Author: Gampol Thitinilnithi, // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("อาทิตย์", "จันทร์", "อังคาร", "พุธ", "พฤหัสบดี", "ศุกร์", "เสาร์", "อาทิตย์"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส.", "อา."); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม"); // short month names Calendar._SMN = new Array ("ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "เกี่ยวกับปฏิทิน"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "ปีที่แล้ว (ถ้ากดค้างจะมีเมนู)"; Calendar._TT["PREV_MONTH"] = "เดือนที่แล้ว (ถ้ากดค้างจะมีเมนู)"; Calendar._TT["GO_TODAY"] = "ไปที่วันนี้"; Calendar._TT["NEXT_MONTH"] = "เดือนหน้า (ถ้ากดค้างจะมีเมนู)"; Calendar._TT["NEXT_YEAR"] = "ปีหน้า (ถ้ากดค้างจะมีเมนู)"; Calendar._TT["SEL_DATE"] = "เลือกวัน"; Calendar._TT["DRAG_TO_MOVE"] = "กดแล้วลากเพื่อย้าย"; Calendar._TT["PART_TODAY"] = " (วันนี้)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "แสดง %s เป็นวันแรก"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "ปิด"; Calendar._TT["TODAY"] = "วันนี้"; Calendar._TT["TIME_PART"] = "(Shift-)กดหรือกดแล้วลากเพื่อเปลี่ยนค่า"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a %e %b"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "เวลา:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-tr.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"); // short month names Calendar._SMN = new Array ("Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Takvim hakkında"; Calendar._TT["ABOUT"] = "DHTML Tarih/Zaman Seçici\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Tarih Seçimi:\n" + "- Yıl seçmek için \xab, \xbb tuşlarını kullanın\n" + "- Ayı seçmek için " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " tuşlarını kullanın\n" + "- Hızlı seçim için yukardaki butonların üzerinde farenin tuşuna basılı tutun."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Zaman Seçimi:\n" + "- Arttırmak için herhangi bir zaman bölümüne tıklayın\n" + "- ya da azaltmak için Shift+tıkla yapın\n" + "- ya da daha hızlı bir seçim için tıklayın ve sürükleyin."; Calendar._TT["PREV_YEAR"] = "Öncki yıl (Menu için basılı tutun)"; Calendar._TT["PREV_MONTH"] = "Önceki ay (Menu için basılı tutun)"; Calendar._TT["GO_TODAY"] = "Bugüne Git"; Calendar._TT["NEXT_MONTH"] = "Sonraki Ay (Menu için basılı tutun)"; Calendar._TT["NEXT_YEAR"] = "Next year (Menu için basılı tutun)"; Calendar._TT["SEL_DATE"] = "Tarih seçin"; Calendar._TT["DRAG_TO_MOVE"] = "Taşımak için sürükleyin"; Calendar._TT["PART_TODAY"] = " (bugün)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "%s : önce göster"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "1,0"; Calendar._TT["CLOSE"] = "Kapat"; Calendar._TT["TODAY"] = "Bugün"; Calendar._TT["TIME_PART"] = "Değeri değiştirmek için (Shift-)tıkla veya sürükle"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "Hafta"; Calendar._TT["TIME"] = "Saat:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-uk.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"); // short month names Calendar._SMN = new Array ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "About the calendar"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + "- Use the \xab, \xbb buttons to select year\n" + "- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + "- Hold mouse button on any of the above buttons for faster selection."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Time selection:\n" + "- Click on any of the time parts to increase it\n" + "- or Shift-click to decrease it\n" + "- or click and drag for faster selection."; Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; Calendar._TT["GO_TODAY"] = "Go Today"; Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; Calendar._TT["SEL_DATE"] = "Select date"; Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; Calendar._TT["PART_TODAY"] = " (today)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Display %s first"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Close"; Calendar._TT["TODAY"] = "Today"; Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Time:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-vi.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("Chủ nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy", "Chủ Nhật"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("C.Nhật", "Hai", "Ba", "Tư", "Năm", "Sáu", "Bảy", "C.Nhật"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 1; // full month names Calendar._MN = new Array ("Tháng Giêng", "Tháng Hai", "Tháng Ba", "Tháng Tư", "Tháng Năm", "Tháng Sáu", "Tháng Bảy", "Tháng Tám", "Tháng Chín", "Tháng Mười", "Tháng M.Một", "Tháng Chạp"); // short month names Calendar._SMN = new Array ("Mmột", "Hai", "Ba", "Tư", "Năm", "Sáu", "Bảy", "Tám", "Chín", "Mười", "MMột", "Chạp"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "Giới thiệu"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector (c) dynarch.com 2002-2005 / Tác giả: Mihai Bazon. " + // don't translate this this ;-) "Phiên bản mới nhất có tại: http://www.dynarch.com/projects/calendar/. " + "Sản phẩm được phân phối theo giấy phép GNU LGPL. Xem chi tiết tại http://gnu.org/licenses/lgpl.html." + "\n\n" + "Chọn ngày:\n" + "- Dùng nút \xab, \xbb để chọn năm\n" + "- Dùng nút " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " để chọn tháng\n" + "- Giữ chuột vào các nút trên để có danh sách năm và tháng."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Chọn thời gian:\n" + "- Click chuột trên từng phần của thời gian để chỉnh sửa\n" + "- hoặc nhấn Shift + click chuột để tăng giá trị\n" + "- hoặc click chuột và kéo (drag) để chọn nhanh."; Calendar._TT["PREV_YEAR"] = "Năm trước (giữ chuột để có menu)"; Calendar._TT["PREV_MONTH"] = "Tháng trước (giữ chuột để có menu)"; Calendar._TT["GO_TODAY"] = "đến Hôm nay"; Calendar._TT["NEXT_MONTH"] = "Tháng tới (giữ chuột để có menu)"; Calendar._TT["NEXT_YEAR"] = "Ngày tới (giữ chuột để có menu)"; Calendar._TT["SEL_DATE"] = "Chọn ngày"; Calendar._TT["DRAG_TO_MOVE"] = "Kéo (drag) để di chuyển"; Calendar._TT["PART_TODAY"] = " (hôm nay)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "Hiển thị %s trước"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Đóng"; Calendar._TT["TODAY"] = "Hôm nay"; Calendar._TT["TIME_PART"] = "Click, shift-click hoặc kéo (drag) để đổi giá trị"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; Calendar._TT["TIME"] = "Time:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-zh-tw.js ================================================ // ** I18N // Calendar EN language // Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("日", "一", "二", "三", "四", "五", "六", "日"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"); // short month names Calendar._SMN = new Array ("一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "關於 calendar"; Calendar._TT["ABOUT"] = "DHTML 日期/時間 選擇器\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "最新版本取得位址: http://www.dynarch.com/projects/calendar/\n" + "使用 GNU LGPL 發行. 參考 http://gnu.org/licenses/lgpl.html 以取得更多關於 LGPL 之細節。" + "\n\n" + "日期選擇方式:\n" + "- 使用滑鼠點擊 \xab 、 \xbb 按鈕選擇年份\n" + "- 使用滑鼠點擊 " + String.fromCharCode(0x2039) + " 、 " + String.fromCharCode(0x203a) + " 按鈕選擇月份\n" + "- 使用滑鼠點擊上述按鈕並按住不放,可開啟快速選單。"; Calendar._TT["ABOUT_TIME"] = "\n\n" + "時間選擇方式:\n" + "- 「單擊」時分秒為遞增\n" + "- 或 「Shift-單擊」為遞減\n" + "- 或 「單擊且拖拉」為快速選擇"; Calendar._TT["PREV_YEAR"] = "前一年 (按住不放可顯示選單)"; Calendar._TT["PREV_MONTH"] = "前一個月 (按住不放可顯示選單)"; Calendar._TT["GO_TODAY"] = "選擇今天"; Calendar._TT["NEXT_MONTH"] = "後一個月 (按住不放可顯示選單)"; Calendar._TT["NEXT_YEAR"] = "下一年 (按住不放可顯式選單)"; Calendar._TT["SEL_DATE"] = "請點選日期"; Calendar._TT["DRAG_TO_MOVE"] = "按住不放可拖拉視窗"; Calendar._TT["PART_TODAY"] = " (今天)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "以 %s 做為一週的首日"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "關閉視窗"; Calendar._TT["TODAY"] = "今天"; Calendar._TT["TIME_PART"] = "(Shift-)加「單擊」或「拖拉」可變更值"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "星期 %a, %b %e 日"; Calendar._TT["WK"] = "週"; Calendar._TT["TIME"] = "時間:"; ================================================ FILE: public/javascripts/calendar/lang/calendar-zh.js ================================================ // ** I18N // Calendar Chinese language // Author: Andy Wu, // Encoding: any // Distributed under the same terms as the calendar itself. // For translators: please use UTF-8 if possible. We strongly believe that // Unicode is the answer to a real internationalized world. Also please // include your contact information in the header, as can be seen above. // full day names Calendar._DN = new Array ("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"); // Please note that the following array of short day names (and the same goes // for short month names, _SMN) isn't absolutely necessary. We give it here // for exemplification on how one can customize the short day names, but if // they are simply the first N letters of the full name you can simply say: // // Calendar._SDN_len = N; // short day name length // Calendar._SMN_len = N; // short month name length // // If N = 3 then this is not needed either since we assume a value of 3 if not // present, to be compatible with translation files that were written before // this feature. // short day names Calendar._SDN = new Array ("日", "一", "二", "三", "四", "五", "六", "日"); // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. Calendar._FD = 0; // full month names Calendar._MN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); // short month names Calendar._SMN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); // tooltips Calendar._TT = {}; Calendar._TT["INFO"] = "关于日历"; Calendar._TT["ABOUT"] = "DHTML 日期/时间 选择器\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) "最新版本请访问: http://www.dynarch.com/projects/calendar/\n" + "遵循 GNU LGPL 发布。详情请查阅 http://gnu.org/licenses/lgpl.html " + "\n\n" + "日期选择:\n" + "- 使用 \xab,\xbb 按钮选择年\n" + "- 使用 " + String.fromCharCode(0x2039) + "," + String.fromCharCode(0x203a) + " 按钮选择月\n" + "- 在上述按钮上按住不放可以快速选择"; Calendar._TT["ABOUT_TIME"] = "\n\n" + "时间选择:\n" + "- 点击时间的任意部分来增加\n" + "- Shift加点击来减少\n" + "- 点击后拖动进行快速选择"; Calendar._TT["PREV_YEAR"] = "上年(按住不放显示菜单)"; Calendar._TT["PREV_MONTH"] = "上月(按住不放显示菜单)"; Calendar._TT["GO_TODAY"] = "回到今天"; Calendar._TT["NEXT_MONTH"] = "下月(按住不放显示菜单)"; Calendar._TT["NEXT_YEAR"] = "下年(按住不放显示菜单)"; Calendar._TT["SEL_DATE"] = "选择日期"; Calendar._TT["DRAG_TO_MOVE"] = "拖动"; Calendar._TT["PART_TODAY"] = " (今日)"; // the following is to inform that "%s" is to be the first day of week // %s will be replaced with the day name. Calendar._TT["DAY_FIRST"] = "一周开始于 %s"; // This may be locale-dependent. It specifies the week-end days, as an array // of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 // means Monday, etc. Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "关闭"; Calendar._TT["TODAY"] = "今天"; Calendar._TT["TIME_PART"] = "Shift加点击或者拖动来变更"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; Calendar._TT["TT_DATE_FORMAT"] = "星期%a %b%e日"; Calendar._TT["WK"] = "周"; Calendar._TT["TIME"] = "时间:"; ================================================ FILE: public/javascripts/context_menu.js ================================================ /* BetterMeans - Work 2.0 Copyright (C) 2006-2011 See readme for details and license */ var observingContextMenuClick; ContextMenu = Class.create(); ContextMenu.prototype = { initialize: function (url) { this.url = url; // prevent text selection in the issue list var tables = $$('table.issues'); for (i=0; i window_width) { render_x -= menu_width; $('context-menu').addClassName('reverse-x'); } else { $('context-menu').removeClassName('reverse-x'); } if (max_height > window_height) { render_y -= menu_height; $('context-menu').addClassName('reverse-y'); } else { $('context-menu').removeClassName('reverse-y'); } if (render_x <= 0) render_x = 1; if (render_y <= 0) render_y = 1; $('context-menu').style['left'] = (render_x + 'px'); $('context-menu').style['top'] = (render_y + 'px'); Effect.Appear('context-menu', {duration: 0.20}); if (window.parseStylesheets) { window.parseStylesheets(); } // IE }}) }, hideMenu: function() { Element.hide('context-menu'); }, addSelection: function(tr) { tr.addClassName('context-menu-selection'); this.checkSelectionBox(tr, true); }, toggleSelection: function(tr) { if (this.isSelected(tr)) { this.removeSelection(tr); } else { this.addSelection(tr); } }, removeSelection: function(tr) { tr.removeClassName('context-menu-selection'); this.checkSelectionBox(tr, false); }, unselectAll: function() { var rows = $$('.hascontextmenu'); for (i=0; i 0) { inputs[0].checked = checked; } }, isSelected: function(tr) { return Element.hasClassName(tr, 'context-menu-selection'); } } function toggleIssuesSelection(el) { var boxes = el.getElementsBySelector('input[type=checkbox]'); var all_checked = true; for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } for (i = 0; i < boxes.length; i++) { if (all_checked) { boxes[i].checked = false; boxes[i].up('tr').removeClassName('context-menu-selection'); } else if (boxes[i].checked == false) { boxes[i].checked = true; boxes[i].up('tr').addClassName('context-menu-selection'); } } } function window_size() { var w; var h; if (window.innerWidth) { w = window.innerWidth; h = window.innerHeight; } else if (document.documentElement) { w = document.documentElement.clientWidth; h = document.documentElement.clientHeight; } else { w = document.body.clientWidth; h = document.body.clientHeight; } return {width: w, height: h}; } ================================================ FILE: public/javascripts/controls.js ================================================ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // // Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. // // Tokenized incremental autocompletion is enabled automatically // when an autocompleter is instantiated with the 'tokens' option // in the options parameter, e.g.: // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { element = $(element); this.element = element; this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; if(this.setOptions) this.setOptions(options); else this.options = options || { }; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; this.element.setAttribute('autocomplete','off'); Element.hide(this.update); Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); }, show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; this.update.style.zIndex = 2; Element.show(this.iefix); }, hide: function() { this.stopIndicator(); if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, startIndicator: function() { if(this.options.indicator) Element.show(this.options.indicator); }, stopIndicator: function() { if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { if(this.active) switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: this.markPrevious(); this.render(); Event.stop(event); return; case Event.KEY_DOWN: this.markNext(); this.render(); Event.stop(event); return; } else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, activate: function() { this.changed = false; this.hasFocus = true; this.getUpdatedChoices(); }, onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; this.active = false; }, render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) this.index==i ? Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); if(this.hasFocus) { this.show(); this.active = true; } } else { this.active = false; this.hide(); } }, markPrevious: function() { if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, markNext: function() { if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, getCurrentEntry: function() { return this.getEntry(this.index); }, selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); }, updateElement: function(selectedElement) { if (this.options.updateElement) { this.options.updateElement(selectedElement); return; } var value = ''; if (this.options.select) { var nodes = $(selectedElement).select('.' + this.options.select) || []; if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); if (whitespace) newValue += whitespace[0]; this.element.value = newValue + value + this.element.value.substr(bounds[1]); } else { this.element.value = value; } this.oldElementValue = this.element.value; this.element.focus(); if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, updateChoices: function(choices) { if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); } else { this.render(); } } }, addObservers: function(element) { Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); Event.observe(element, "click", this.onClick.bindAsEventListener(this)); }, onObserverEvent: function() { this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); } else { this.active = false; this.hide(); } this.oldElementValue = this.element.value; }, getToken: function() { var bounds = this.getTokenBounds(); return this.element.value.substring(bounds[0], bounds[1]).strip(); }, getTokenBounds: function() { if (null != this.tokenBounds) return this.tokenBounds; var value = this.element.value; if (value.strip().empty()) return [-1, 0]; var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); var offset = (diff == this.oldElementValue.length ? 1 : 0); var prevTokenPos = -1, nextTokenPos = value.length; var tp; for (var index = 0, l = this.options.tokens.length; index < l; ++index) { tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); if (tp > prevTokenPos) prevTokenPos = tp; tp = value.indexOf(this.options.tokens[index], diff + offset); if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; } return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); } }); Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { var boundary = Math.min(newS.length, oldS.length); for (var index = 0; index < boundary; ++index) if (newS[index] != oldS[index]) return index; return boundary; }; Ajax.Autocompleter = Class.create(Autocompleter.Base, { initialize: function(element, update, url, options) { this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, getUpdatedChoices: function() { this.startIndicator(); var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; new Ajax.Request(this.url, this.options); }, onComplete: function(request) { this.updateChoices(request.responseText); } }); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather // than sending out Ajax queries, which can be quite slow sometimes. // // The constructor takes four parameters. The first two are, as usual, // the id of the monitored textbox, and id of the autocompletion menu. // The third is the array you want to autocomplete from, and the fourth // is the options block. // // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set // the option fullSearch to true (default: off). // // - fullSsearch - Search anywhere in autocomplete array strings. // // - partialChars - How many characters to enter before triggering // a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. Autocompleter.Local = Class.create(Autocompleter.Base, { initialize: function(element, update, array, options) { this.baseInitialize(element, update, options); this.options.array = array; }, getUpdatedChoices: function() { this.updateChoices(this.options.selector(this)); }, setOptions: function(options) { this.options = Object.extend({ choices: 10, partialSearch: true, partialChars: 2, ignoreCase: true, fullSearch: false, selector: function(instance) { var ret = []; // Beginning matches var partial = []; // Inside matches var entry = instance.getToken(); var count = 0; for (var i = 0; i < instance.options.array.length && ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + elem.substr(foundPos, entry.length) + "" + elem.substr( foundPos + entry.length) + "
  • "); break; } } foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "
      " + ret.join('') + "
    "; } }, options || { }); } }); // AJAX in-place editor and collection editor // Full rewrite by Christophe Porteneuve (April 2007). // Use this if you notice weird scrolling problems on some browsers, // the DOM might be a bit confused when this gets called so do this // waits 1 ms (with setTimeout) until it does the activation Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); }; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { this.url = url; this.element = element = $(element); this.prepareOptions(); this._controls = { }; arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! Object.extend(this.options, options || { }); if (!this.options.formId && this.element.id) { this.options.formId = this.element.id + '-inplaceeditor'; if ($(this.options.formId)) this.options.formId = ''; } if (this.options.externalControl) this.options.externalControl = $(this.options.externalControl); if (!this.options.externalControl) this.options.externalControlOnly = false; this._originalBackground = this.element.getStyle('background-color') || 'transparent'; this.element.title = this.options.clickToEditText; this._boundCancelHandler = this.handleFormCancellation.bind(this); this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); this._boundFailureHandler = this.handleAJAXFailure.bind(this); this._boundSubmitHandler = this.handleFormSubmission.bind(this); this._boundWrapperHandler = this.wrapUp.bind(this); this.registerListeners(); }, checkForEscapeOrReturn: function(e) { if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; if (Event.KEY_ESC == e.keyCode) this.handleFormCancellation(e); else if (Event.KEY_RETURN == e.keyCode) this.handleFormSubmission(e); }, createControl: function(mode, handler, extraClasses) { var control = this.options[mode + 'Control']; var text = this.options[mode + 'Text']; if ('button' == control) { var btn = document.createElement('input'); btn.type = 'submit'; btn.value = text; btn.className = 'editor_' + mode + '_button'; if ('cancel' == mode) btn.onclick = this._boundCancelHandler; this._form.appendChild(btn); this._controls[mode] = btn; } else if ('link' == control) { var link = document.createElement('a'); link.href = '#'; link.appendChild(document.createTextNode(text)); link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; link.className = 'editor_' + mode + '_link'; if (extraClasses) link.className += ' ' + extraClasses; this._form.appendChild(link); this._controls[mode] = link; } }, createEditField: function() { var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); var fld; if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { fld = document.createElement('input'); fld.type = 'text'; var size = this.options.size || this.options.cols || 0; if (0 < size) fld.size = size; } else { fld = document.createElement('textarea'); fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); fld.cols = this.options.cols || 40; } fld.name = this.options.paramName; fld.value = text; // No HTML breaks conversion anymore fld.className = 'editor_field'; if (this.options.submitOnBlur) fld.onblur = this._boundSubmitHandler; this._controls.editor = fld; if (this.options.loadTextURL) this.loadExternalText(); this._form.appendChild(this._controls.editor); }, createForm: function() { var ipe = this; function addText(mode, condition) { var text = ipe.options['text' + mode + 'Controls']; if (!text || condition === false) return; ipe._form.appendChild(document.createTextNode(text)); }; this._form = $(document.createElement('form')); this._form.id = this.options.formId; this._form.addClassName(this.options.formClassName); this._form.onsubmit = this._boundSubmitHandler; this.createEditField(); if ('textarea' == this._controls.editor.tagName.toLowerCase()) this._form.appendChild(document.createElement('br')); if (this.options.onFormCustomization) this.options.onFormCustomization(this, this._form); addText('Before', this.options.okControl || this.options.cancelControl); this.createControl('ok', this._boundSubmitHandler); addText('Between', this.options.okControl && this.options.cancelControl); this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); addText('After', this.options.okControl || this.options.cancelControl); }, destroy: function() { if (this._oldInnerHTML) this.element.innerHTML = this._oldInnerHTML; this.leaveEditMode(); this.unregisterListeners(); }, enterEditMode: function(e) { if (this._saving || this._editing) return; this._editing = true; this.triggerCallback('onEnterEditMode'); if (this.options.externalControl) this.options.externalControl.hide(); this.element.hide(); this.createForm(); this.element.parentNode.insertBefore(this._form, this.element); if (!this.options.loadTextURL) this.postProcessEditField(); if (e) Event.stop(e); }, enterHover: function(e) { if (this.options.hoverClassName) this.element.addClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onEnterHover'); }, getText: function() { return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); if (this._oldInnerHTML) { this.element.innerHTML = this._oldInnerHTML; this._oldInnerHTML = null; } }, handleFormCancellation: function(e) { this.wrapUp(); if (e) Event.stop(e); }, handleFormSubmission: function(e) { var form = this._form; var value = $F(this._controls.editor); this.prepareSubmission(); var params = this.options.callback(form, value) || ''; if (Object.isString(params)) params = params.toQueryParams(); params.editorId = this.element.id; if (this.options.htmlResponse) { var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Updater({ success: this.element }, this.url, options); } else { var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: params, onComplete: this._boundWrapperHandler, onFailure: this._boundFailureHandler }); new Ajax.Request(this.url, options); } if (e) Event.stop(e); }, leaveEditMode: function() { this.element.removeClassName(this.options.savingClassName); this.removeForm(); this.leaveHover(); this.element.style.backgroundColor = this._originalBackground; this.element.show(); if (this.options.externalControl) this.options.externalControl.show(); this._saving = false; this._editing = false; this._oldInnerHTML = null; this.triggerCallback('onLeaveEditMode'); }, leaveHover: function(e) { if (this.options.hoverClassName) this.element.removeClassName(this.options.hoverClassName); if (this._saving) return; this.triggerCallback('onLeaveHover'); }, loadExternalText: function() { this._form.addClassName(this.options.loadingClassName); this._controls.editor.disabled = true; var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._form.removeClassName(this.options.loadingClassName); var text = transport.responseText; if (this.options.stripLoadedTextTags) text = text.stripTags(); this._controls.editor.value = text; this._controls.editor.disabled = false; this.postProcessEditField(); }.bind(this), onFailure: this._boundFailureHandler }); new Ajax.Request(this.options.loadTextURL, options); }, postProcessEditField: function() { var fpc = this.options.fieldPostCreation; if (fpc) $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); }, prepareOptions: function() { this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); [this._extraDefaultOptions].flatten().compact().each(function(defs) { Object.extend(this.options, defs); }.bind(this)); }, prepareSubmission: function() { this._saving = true; this.removeForm(); this.leaveHover(); this.showSaving(); }, registerListeners: function() { this._listeners = { }; var listener; $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { listener = this[pair.value].bind(this); this._listeners[pair.key] = listener; if (!this.options.externalControlOnly) this.element.observe(pair.key, listener); if (this.options.externalControl) this.options.externalControl.observe(pair.key, listener); }.bind(this)); }, removeForm: function() { if (!this._form) return; this._form.remove(); this._form = null; this._controls = { }; }, showSaving: function() { this._oldInnerHTML = this.element.innerHTML; this.element.innerHTML = this.options.savingText; this.element.addClassName(this.options.savingClassName); this.element.style.backgroundColor = this._originalBackground; this.element.show(); }, triggerCallback: function(cbName, arg) { if ('function' == typeof this.options[cbName]) { this.options[cbName](this, arg); } }, unregisterListeners: function() { $H(this._listeners).each(function(pair) { if (!this.options.externalControlOnly) this.element.stopObserving(pair.key, pair.value); if (this.options.externalControl) this.options.externalControl.stopObserving(pair.key, pair.value); }.bind(this)); }, wrapUp: function(transport) { this.leaveEditMode(); // Can't use triggerCallback due to backward compatibility: requires // binding + direct element this._boundComplete(transport, this.element); } }); Object.extend(Ajax.InPlaceEditor.prototype, { dispose: Ajax.InPlaceEditor.prototype.destroy }); Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { initialize: function($super, element, url, options) { this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; $super(element, url, options); }, createEditField: function() { var list = document.createElement('select'); list.name = this.options.paramName; list.size = 1; this._controls.editor = list; this._collection = this.options.collection || []; if (this.options.loadCollectionURL) this.loadCollection(); else this.checkForExternalText(); this._form.appendChild(this._controls.editor); }, loadCollection: function() { this._form.addClassName(this.options.loadingClassName); this.showLoadingText(this.options.loadingCollectionText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadCollectionURL, options); }, showLoadingText: function(text) { this._controls.editor.disabled = true; var tempOption = this._controls.editor.firstChild; if (!tempOption) { tempOption = document.createElement('option'); tempOption.value = ''; this._controls.editor.appendChild(tempOption); tempOption.selected = true; } tempOption.update((text || '').stripScripts().stripTags()); }, checkForExternalText: function() { this._text = this.getText(); if (this.options.loadTextURL) this.loadExternalText(); else this.buildOptionList(); }, loadExternalText: function() { this.showLoadingText(this.options.loadingText); var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); Object.extend(options, { parameters: 'editorId=' + encodeURIComponent(this.element.id), onComplete: Prototype.emptyFunction, onSuccess: function(transport) { this._text = transport.responseText.strip(); this.buildOptionList(); }.bind(this), onFailure: this.onFailure }); new Ajax.Request(this.options.loadTextURL, options); }, buildOptionList: function() { this._form.removeClassName(this.options.loadingClassName); this._collection = this._collection.map(function(entry) { return 2 === entry.length ? entry : [entry, entry].flatten(); }); var marker = ('value' in this.options) ? this.options.value : this._text; var textFound = this._collection.any(function(entry) { return entry[0] == marker; }.bind(this)); this._controls.editor.update(''); var option; this._collection.each(function(entry, index) { option = document.createElement('option'); option.value = entry[0]; option.selected = textFound ? entry[0] == marker : 0 == index; option.appendChild(document.createTextNode(entry[1])); this._controls.editor.appendChild(option); }.bind(this)); this._controls.editor.disabled = false; Field.scrollFreeActivate(this._controls.editor); } }); //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** //**** This only exists for a while, in order to let **** //**** users adapt to the new API. Read up on the new **** //**** API and convert your code to it ASAP! **** Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { if (!options) return; function fallback(name, expr) { if (name in options || expr === undefined) return; options[name] = expr; }; fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : options.cancelLink == options.cancelButton == false ? false : undefined))); fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : options.okLink == options.okButton == false ? false : undefined))); fallback('highlightColor', options.highlightcolor); fallback('highlightEndColor', options.highlightendcolor); }; Object.extend(Ajax.InPlaceEditor, { DefaultOptions: { ajaxOptions: { }, autoRows: 3, // Use when multi-line w/ rows == 1 cancelControl: 'link', // 'link'|'button'|false cancelText: 'cancel', clickToEditText: 'Click to edit', externalControl: null, // id|elt externalControlOnly: false, fieldPostCreation: 'activate', // 'activate'|'focus'|false formClassName: 'inplaceeditor-form', formId: null, // id|elt highlightColor: '#ffff99', highlightEndColor: '#ffffff', hoverClassName: '', htmlResponse: true, loadingClassName: 'inplaceeditor-loading', loadingText: 'Loading...', okControl: 'button', // 'link'|'button'|false okText: 'ok', paramName: 'value', rows: 1, // If 1 and multi-line, uses autoRows savingClassName: 'inplaceeditor-saving', savingText: 'Saving...', size: 0, stripLoadedTextTags: false, submitOnBlur: false, textAfterControls: '', textBeforeControls: '', textBetweenControls: '' }, DefaultCallbacks: { callback: function(form) { return Form.serialize(form); }, onComplete: function(transport, element) { // For backward compatibility, this one is bound to the IPE, and passes // the element directly. It was too often customized, so we don't break it. new Effect.Highlight(element, { startcolor: this.options.highlightColor, keepBackgroundImage: true }); }, onEnterEditMode: null, onEnterHover: function(ipe) { ipe.element.style.backgroundColor = ipe.options.highlightColor; if (ipe._effect) ipe._effect.cancel(); }, onFailure: function(transport, ipe) { alert('Error communication with the server: ' + transport.responseText.stripTags()); }, onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. onLeaveEditMode: null, onLeaveHover: function(ipe) { ipe._effect = new Effect.Highlight(ipe.element, { startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, restorecolor: ipe._originalBackground, keepBackgroundImage: true }); } }, Listeners: { click: 'enterEditMode', keydown: 'checkForEscapeOrReturn', mouseover: 'enterHover', mouseout: 'leaveHover' } }); Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; // Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields Form.Element.DelayedObserver = Class.create({ initialize: function(element, delay, callback) { this.delay = delay || 0.5; this.element = $(element); this.callback = callback; this.timer = null; this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { if(this.lastValue == $F(this.element)) return; if(this.timer) clearTimeout(this.timer); this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); this.lastValue = $F(this.element); }, onTimerEvent: function() { this.timer = null; this.callback(this.element, $F(this.element)); } }); ================================================ FILE: public/javascripts/dashboard.js ================================================ var D = []; //all data var R = []; //all retrospectives var local_D = null; // var local_R = null; var MAX_REQUESTS_PER_PERSON = 4; var TIMER_INTERVAL = 15000; //15 seconds var INACTIVITY_THRESHOLD = 300000; //5 minutes var timer_active = false; var ITEMHASH = new Array(); //mapping between item IDs and their id in the D array var keyboard_shortcuts = false; var searching = false; //true when user is entering text in search box var default_new_title = 'Enter Title Here'; var new_comment_text = 'Add new comment'; var new_todo_text = 'Add todo'; // var panel_height = $(window).height() - $('.gt-hd').height() - $('#help_section').height() + 28;// + $('.gt-footer').height() ; var panel_height = $(window).height() - $('.gt-hd').height() + 28;// + $('.gt-footer').height() ; var last_activity = new Date(); //tracks last activity of mouse or keyboard click. Used to turn off server polling var last_data_pull = new Date(); //tracks last data recieved from server var highest_pri = -9999; var loaded_panels = 0; //keeps track of how many panels have had their data loaded var local_store = null; //local persistant storage var ok_to_save_local_data = false; var complexity_description = ['Real Easy','.','.','Average','.','.','Super Hard']; var new_attachments = []; //stores ids of attachments to a new item var timer_started = false; $(window).bind('resize', function() { resize(); }); $.fn.makeAbsolute = function(rebase) { return this.each(function() { var el = $(this); var pos = el.position(); el.css({ position: "absolute", marginLeft: 0, marginTop: 0, top: pos.top, left: pos.left }); if (rebase) el.remove().appendTo("body"); }); }; $.fn.watermark = function(css, text) { $(this).focus(function() { $(this).filter(function() { return $(this).val() == "" || $(this).val() == text; }).removeClass(css).val(""); }); $(this).blur(function() { $(this).filter(function() { return $(this).val() == ""; }).addClass(css).val(text); }); var input = $(this); $(this).closest("form").submit(function() { input.filter(function() { return $(this).val() == text; }).val(""); }); $(this).addClass(css).val(text); }; $.fn.keyboard_sensitive = function() { $(this).focus(function() { keyboard_shortcuts = false; }); $(this).blur(function() { keyboard_shortcuts = true; }); }; function start(){ disable_refresh_button(); arm_checkboxes(); set_sub_toggle(); timer_active = false; //stop timer from starting until data loads $('.help-section-link').bind('click',function() { resize(); }); $("#dash_key").mybubbletip('#help_key', {deltaDirection: 'right', bindShow: 'click'}); $('#fast_search').watermark('watermark','Quick Filter'); //Checking for single issue display if (show_issue_id){ show_issue_full(show_issue_id); $("#load_dashboard").show(); $("#loading").hide(); } else if (show_retro_id){ show_retro_full(show_retro_id); $("#load_dashboard").show(); $("#loading").hide(); } else{ load_dashboard(); } } //Chooses setting of sub workstream toggle button and adds events to it function set_sub_toggle(){ var include = $.cookie('include_sub' + currentUserId); if (include == null){ include = 'true'; } if (include == 'true'){ $("#toggle_sub_on").click(); } else{ $("#toggle_sub_off").click(); } $("#toggle_sub_on").click(function(){ $.cookie('include_sub' + currentUserId, 'true', { expires: 365 }); refresh_local_data(); }); $("#toggle_sub_off").click(function(){ $.cookie('include_sub' + currentUserId, 'false', { expires: 365 }); refresh_local_data(); }); } function load_dashboard(){ //prepares ui for page prepare_page(); load_dashboard_data(); start_timer(); $(document).keyup(function(e){ last_activity = new Date(); start_timer(); if (searching){ var text = $('#fast_search').val(); search_for(text); } }); $(document).click(function(e) { start_timer(); last_activity = new Date(); }); $(document).focus(function(e) { start_timer(); last_activity = new Date(); }); } //For IE explorer handling of xml function parse_xml(xml){ if (jQuery.browser.msie) { var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.loadXML(xml); xml = xmlDoc; } return xml; } function load_dashboard_data(){ $("#load_dashboard").hide(); $("#loading").hide(); get_local_data(); // if (local_R == null){ // local_R = []; // } if (local_D != null){ data_ready(local_D,'all'); // if (credits_enabled){ // retros_ready(local_R); // load_retros(); // } ISSUE_COUNT = -1; //we are loading from local data, so we set the counter past 10 to refresh moved items timer_active = true; new_dash_data(); local_D = null; // local_R = null; } else{ D = []; R = []; keyboard_shortcuts = false; ISSUE_COUNT = 0; load_dashboard_data_for_statuses('10,11','new'); load_dashboard_data_for_statuses('1,6','open'); load_dashboard_data_for_statuses('4','inprogress'); load_dashboard_data_for_statuses('8,14,13','done'); load_dashboard_data_for_statuses('9','canceled'); // load_dashboard_data_for_statuses('12','archived'); } ok_to_save_local_data = true; } function refresh_local_data(){ $("#loading_error").hide(); timer_active = false; disable_refresh_button(); clear_filters(); try{ store.set('D_' + projectId, null); store.set('R_' + projectId, null); store.set('lata_data_pull_' + projectId, null); } catch(err){ return; } wipe_panels(); display_panels(); recalculate_widths(); load_dashboard_data(); // enable_refresh_button(); } function save_local_data(){ if (ok_to_save_local_data == false) {return false;} try{ store.set('D_' + projectId,JSON.stringify(D)); store.set('R_' + projectId,JSON.stringify(R)); store.set('last_data_pull_' + projectId,last_data_pull); store.set('includes_sub_workstreams' + projectId, ($('#include_subworkstreams_checkbox').attr("checked") == true)); return true; } catch(err){ return false; } } function get_local_data(){ try{ includes_subs = store.get('includes_sub_workstreams' + projectId); //don't use local data if stored includes subs but requested data doesn't, or vise versa if (includes_subs != String($('#include_subworkstreams_checkbox').attr("checked") == true)){ return false; } local_D = JSON.parse(store.get('D_' + projectId)); if (local_D == null) {return false;} // local_R = JSON.parse(store.get('R_' + projectId)); // if (local_R == null) {local_R = [];} last_data_pull = new Date(store.get('last_data_pull_' + projectId)); //refresh local data since latest code update that require data structure to be updated if (Date.parse(LAST_LOCAL_DB_CHANGE) > last_data_pull){ return false; } else{ return true; } } catch(err){ return false; } } function load_dashboard_data_for_statuses(status_ids,name){ var url = url_for({ controller: 'projects', action : 'dashdata', id : projectId }); // var url = url + '?status_ids=1,4,6,8,10,11,13,14'; url = url + '?status_ids=' + status_ids; if ($('#include_subworkstreams_checkbox').attr("checked") == true){ url = url + "&include_subworkstreams=true"; } $.ajax({ type: "GET", dataType: "json", contentType: "application/json", cache:false, data:{}, // dataType: ($.browser.msie) ? "text" : "json", url: url, success: function(html){ last_data_pull = new Date(); ISSUE_COUNT = ISSUE_COUNT + html.length; data_ready(html,name); }, error: function (xhr, textStatus, errorThrown) { // alert(xhr.status); // typically only one of textStatus or errorThrown will have info // possible valuees for textstatus "timeout", "error", "notmodified" and "parsererror $("#loading").hide(); $("#loading_error").show(); }, timeout: 30000 //30 seconds }); } // listens for any navigation keypress activity // $(document).keypress(function(e) // { // if (!keyboard_shortcuts){return;}; // // switch(e.which) // { // // user presses the "a" // case 110: new_item(); // break; // // } // }); function data_ready(html,name){ last_item = D.length; D = D.concat(html); add_items_to_panels(last_item); sort_panels(); if (name == 'all'){ $('#new_close').addClass('closePanel').removeClass('closePanelLoading'); $('#open_close').addClass('closePanel').removeClass('closePanelLoading'); $('#inprogress_close').addClass('closePanel').removeClass('closePanelLoading'); $('#done_close').addClass('closePanel').removeClass('closePanelLoading'); loaded_panels = 6; enable_refresh_button(); timer_active = true; } else{ $('#' + name + '_close').addClass('closePanel').removeClass('closePanelLoading'); loaded_panels = loaded_panels + 1; } update_panel_counts(); prepare_item_lookup_array(); //TODO: move this somewhere else for efficiency. it should only run once // if (loaded_panels == 4 && credits_enabled){ // load_retros(); // timer_active = true; // } if (loaded_panels == 4){ enable_refresh_button(); timer_active = true; } } function replace_reloading_images_for_panels(){ $('.closePanelLoading').addClass('closePanel').removeClass('closePanelLoading'); } // function load_retros(){ // // if (!credits_enabled){ // return false; // } // var url = url_for({ controller: 'projects', // id : projectId // }); // url = url + '/retros/index_json'; // // $.ajax({ // type: "GET", // dataType: "json", // contentType: "application/json", // url: url, // success: function(html){ // retros_ready(html); // enable_refresh_button(); // }, // error: function (XMLHttpRequest, textStatus, errorThrown) { // // typically only one of textStatus or errorThrown will have info // // possible valuees for textstatus "timeout", "error", "notmodified" and "parsererror // $("#loading").hide(); // $("#loading_error").show(); // enable_refresh_button(); // }, // timeout: 30000 //30 seconds // }); // return true; // } // function enable_refresh_button(){ $('#refresh_data').show(); } function disable_refresh_button(){ $('#refresh_data').hide(); } function retros_ready(html,load_remaining_panels){ R = html; insert_retros(); } function insert_retros(){ $('.retrospective').remove(); for(var i = 0; i < R.length; i++ ){ add_retro(i,"top",false); } } function add_retro(rdataId,position,scroll){ var html = generate_retro(rdataId); var panelid = 'done'; if (position=="bottom") { $("#" + panelid + '_end_of_list').append(html); } else if (position=="top") { $("#" + panelid+ '_end_of_list').prepend(html); } if (scroll) { $("#" + panelid + "_items").scrollTo('#item_' + dataId, 100); } } //makes all text boxes sensitive to keyboard shortcuts function make_text_boxes_toggle_keyboard_shortcuts(){ $("input").keyboard_sensitive(); $("textarea").keyboard_sensitive(); } // function load_search(){ // html = ''; // // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + '
    '; // html = html + ' '; // html = html + ' Search'; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + ' '; // html = html + '
    '; // // $('#header').append(html); // } function prepare_page(){ display_panels(); resize(); recalculate_widths(); keyboard_shortcuts = true; make_text_boxes_toggle_keyboard_shortcuts(); } function start_timer(){ if (timer_started == true){ return; } else{ timer_started = true; } $.timer(TIMER_INTERVAL, function (timer) { timer_beat(timer); }); } function stop_timer(timer){ timer_started = false; timer.stop(); } function prepare_item_lookup_array(){ for (var i=0; iRetrospective is open ⇒', rdataId); $('#retro_' + retro.id + '_items').prepend(notice); } for(; i < D.length; i++ ){ add_item(i,"bottom",false,panelid); } update_panel_count(panelid,true); } function show_item_fancybox(dataId){ var itemId = D[dataId].id; var url = url_for({ controller: 'issues', action : 'show', id : itemId }); url = url + '?dataId=' + dataId; show_fancybox(url,'loading data...'); } function show_details_flyover(dataId,callingElement,delayshow){ $('#flyover_' + dataId).remove(); generate_details_flyover(dataId); $('#' + callingElement).bubbletip($('#flyover_' + dataId), { deltaDirection: 'right', delayShow: delayshow, delayHide: 100, offsetLeft: 0 }); } function show_estimate_flyover(dataId,callingElement){ $('#flyover_estimate_' + dataId).remove(); generate_estimate_flyover(dataId); $('#' + callingElement).bubbletip($('#flyover_estimate_' + dataId), { deltaDirection: 'right', delayShow: 0, delayHide: 100, offsetLeft: 0 }); } function show_points_flyover(dataId,callingElement){ $('#flyover_points_' + dataId).remove(); generate_points_flyover(dataId); $('#' + callingElement).bubbletip($('#flyover_points_' + dataId), { deltaDirection: 'right', delayShow: 0, delayHide: 100, offsetLeft: 0 }); } function hide_bubbletips(){ $('.bubbletip').hide(); } function show_pri_flyover(dataId,callingElement){ $('#flyover_pri_' + dataId).remove(); generate_pri_flyover(dataId); $('#' + callingElement).bubbletip($('#flyover_pri_' + dataId), { deltaDirection: 'right', delayShow: 0, delayHide: 100, offsetLeft: 0 }); } function show_agree_flyover(dataId,callingElement){ $('#flyover_agree_' + dataId).remove(); generate_agree_flyover(dataId); $('#' + callingElement).bubbletip($('#flyover_agree_' + dataId), { deltaDirection: 'right', delayShow: 0, delayHide: 100, offsetLeft: 0 }); } function show_accept_flyover(dataId,callingElement){ $('#flyover_accept_' + dataId).remove(); generate_accept_flyover(dataId); //If flyover hasn't already been generated, then generate it! // if ($('#flyover_accept_' + dataId).length == 0){ // generate_accept_flyover(dataId); // } $('#' + callingElement).bubbletip($('#flyover_accept_' + dataId), { deltaDirection: 'right', delayShow: 0, delayHide: 100, offsetLeft: 0 }); } function is_visible(item){ if (item == null) {return false;} return true; } function is_startable(item){ if (item.status.name == "Open"){ return true; // return ((item.pri > (highest_pri - startable_priority_tiers))||(item.pri == 0)); } else{ return false; } } function add_item(dataId,position,scroll,panelid){ if (!is_visible(D[dataId])) {return;} if (!panelid){ //Deciding on wich panel for this item? switch (D[dataId].status.name){ case 'New': panelid= 'new'; break; case 'Estimate': panelid= 'new'; break; case 'Open': panelid= 'new'; break; case 'Committed': panelid = 'inprogress'; break; case 'Done': panelid = 'done'; break; case 'Accepted': panelid = 'done'; break; case 'Rejected': panelid = 'done'; break; case 'Canceled': panelid = 'canceled'; break; case 'Archived': panelid = 'canceled'; break; default : panelid = 'canceled'; } } var html = generate_item(dataId); if (position=="bottom") { $("#" + panelid + '_start_of_list').append(html); } else if (position=="top") { $("#" + panelid+ '_start_of_list').prepend(html); } // else if (position=="pri"){ // $("#" + panelid + "_items").children.each() // } if (scroll) { $("#" + panelid + "_items").scrollTo('#item_' + dataId, 100); } } function generate_estimate_flyover(dataId){ var item = D[dataId]; var i = 0; //counter var credits = item.points; var you_voted = "You haven't estimated yet"; var title = ''; var user_estimate = -100; var total_people_estimating = 0; for(i=0; i < item.issue_votes.length; i++){ if (item.issue_votes[i].vote_type != 4) continue; total_people_estimating++ ; if (currentUserLogin == item.issue_votes[i].user.login){ var user_estimate_text = ""; if (item.issue_votes[i].points == -1){ user_estimate = item.issue_votes[i].points; user_estimate_text = "Don't know"; } else if (credits_enabled){ user_estimate = item.issue_votes[i].points; user_estimate_text = user_estimate + " credits"; } else{ user_estimate = convert_points_to_complexity(item.issue_votes[i].points); user_estimate_text = user_estimate; } you_voted = "Your estimate " + user_estimate_text + " - " + humane_date(item.issue_votes[i].updated_at); } } //If user estimated, or item is in progress, we can see the average if (((item.status.name != 'New')&&(item.status.name != 'Estimate')&&(item.status.name != 'Open')) || (user_estimate != -100)){ if (credits == null){ title = 'No binding estimates yet'; } else if (credits_enabled){ title = 'Avg ' + Math.round(credits) + ' credits (' + total_people_estimating + ' people)'; } else{ title = 'Avg ' + credits_to_points(Math.round(credits),credit_base) + ' points (' + total_people_estimating + ' people)'; } } else{ title = "Vote to see Estimates"; } var history = ''; //Show history if user estimated, or if item is no longer available for estimation if ((user_estimate != -100)||((item.status.name != 'New')&&(item.status.name != 'Estimate')&&(item.status.name != 'Open'))){ for(i = 0; i < item.issue_votes.length; i++ ){ if (item.issue_votes[i].vote_type != 4) continue; if (item.issue_votes[i].points == -1){ history = history + 'Don\'t know - ' + item.issue_votes[i].user.firstname + ' ' + item.issue_votes[i].user.lastname; } else if (credits_enabled){ history = history + item.issue_votes[i].points + ' cr - ' + item.issue_votes[i].user.firstname + ' ' + item.issue_votes[i].user.lastname; } else{ history = history + credits_to_points(Math.round(item.issue_votes[i].points),credit_base) + ' points - ' + item.issue_votes[i].user.firstname + ' ' + item.issue_votes[i].user.lastname; } if (item.issue_votes[i].isbinding == false){ history = history + ' (non-binding)'; } history = history + '
    '; } } var action_header = ''; var buttons = ''; // if (((item.status.name != 'New')&&(item.status.name != 'Estimate')&&(item.status.name != 'Open'))) { user_estimate == -100 ? action_header = 'Make an estimate' : action_header = 'Change your estimate'; buttons = buttons + generate_estimate_button(-1,-1, item.id, dataId, (user_estimate != -100)); for(i = 0; i < point_factor.length; i++ ){ buttons = buttons + generate_estimate_button(i,point_factor[i] * credit_base, item.id, dataId, (user_estimate != -100)); } buttons = buttons + generate_custom_estimate_button(dataId,user_estimate); // } return generate_flyover(dataId,'estimate',title,you_voted,action_header,buttons,history); } function generate_points_flyover(dataId){ var item = D[dataId]; var i = 0; //counter var credits = item.points; var you_voted = "This is a read only value, and cannot be voted on."; var title = item.points + ' credits'; var history = ''; var action_header = ''; var buttons = ''; return generate_flyover(dataId,'points',title,you_voted,action_header,buttons,history); } function prompt_for_number(message,default_data){ var amount=prompt(message,default_data); if (amount == null || amount == '' || (!isNumeric(amount))) { return prompt_for_number(message,default_data); } else { return amount; } } function prompt_for_custom_estimate(dataId,points){ var amount = prompt_for_number("Please enter expense amount in dollars:",points); send_item_action(dataId,'estimate','&points=' + amount); } function generate_custom_estimate_button(dataId,user_estimate){ if (!credits_enabled){ return ''; } var points = 0; if (user_estimate > -1){ points = user_estimate; } var html = '
    '; html = html + 'Custom Credits'; html = html + ' custom amount'; html = html + '
    '; return html; } function convert_points_to_complexity(points){ if (points == -1){ return "Don't know"; } if (points > complexity_description.length - 1){ points = complexity_description.length - 1; } return complexity_description[points]; } function generate_estimate_button(points,credits, itemId, dataId, comment){ var label = ''; if (credits == -1){ label = "Don't know"; } else if (credits_enabled){ label = credits + ' Credits'; } else{ label = convert_points_to_complexity(points); } var html = '
    '; var onclick = 'click_estimate_from_flyover(' + dataId + ',this,\'' + '&points=' + credits + '\',' + comment + ');return false;'; html = html + '' + label + ''; html = html + ' ' + label; html = html + '
    '; return html; } function generate_pri_action(points, itemId, dataId){ var html = '
    ' + pri_text(points) + '
    '; return html; } function pri_text(points){ switch (points){ case 0: return "NEUTRAL"; break; case 1: return "UP"; break; case -1: return "DOWN"; break; } return "ERROR: OUT OF RANGE"; } function agree_text(points){ switch (points){ case 0: return "NEUTRAL"; break; case 1: return "AGREE"; break; case -1: return "DISAGREE"; break; case -9999: return "BLOCK"; break; } return "ERROR: OUT OF RANGE"; } function accept_text(points){ switch (points){ case 0: return "NEUTRAL"; break; case 1: return "ACCEPT"; break; case -1: return "REJECT"; break; case -9999: return "BLOCK"; break; } return "ERROR: OUT OF RANGE"; } function generate_pri_flyover(dataId){ var item = D[dataId]; var points; item.pri == null ? points = 0 : points = item.pri; var you_voted = ''; var user_pri_id = 0; var total_people_prioritizing = 0; var i = 0; //counter variable for(i=0; i < item.issue_votes.length; i++){ if (currentUserLogin == item.issue_votes[i].user.login){ if (item.issue_votes[i].vote_type != 3) continue; total_people_prioritizing++ ; you_voted = "You prioritized " + pri_text(item.issue_votes[i].points) + " - " + humane_date(item.issue_votes[i].updated_at); user_pri_id = item.issue_votes[i].id; } } if (user_pri_id == 0){ you_voted = "You haven't prioritized this item"; } var title = 'Total ' + points + ' points (' + total_people_prioritizing + ' people)'; var action_header = ''; user_pri_id == 0 ? action_header = 'Prioritize' : action_header = 'Change your prioritization:'; var buttons = ''; buttons = buttons + generate_pri_action(1, item.id, dataId) + '
    '; buttons = buttons + generate_pri_action(0, item.id, dataId) + '
    '; buttons = buttons + generate_pri_action(-1, item.id, dataId); var history = ''; if (!(item.issue_votes == null || item.issue_votes.length < 1)){ for(i = 0; i < item.issue_votes.length; i++ ){ if (item.issue_votes[i].vote_type != 3) continue; history = history + pri_text(item.issue_votes[i].points) + ' - ' + item.issue_votes[i].user.firstname + ' ' + item.issue_votes[i].user.lastname; if (item.issue_votes[i].isbinding == false){ history = history + ' (non-binding)'; } history = history + '
    '; } } return generate_flyover(dataId,'pri',title,you_voted,action_header,buttons,history); } function generate_flyover(dataId,type,title,you_voted,action_header,buttons,history){ var html = ''; html = html + ''; $.fancybox( { 'content' : content, 'width' : 'auto', 'height' : 'auto', 'title' : title, 'autoScale' : false, 'transitionIn' : 'none', 'transitionOut' : 'none', 'scrolling' : 'no', 'showCloseButton' : false, 'modal' : true, 'href' : '#comment_prompt' }); $('#new_comment_' + dataId).focus(); $( "#prompt_comment_" + dataId ).mentions(projectId); } function filter_select(){ var selection = $("#filter_select").val(); $('#fast_search').val(''); //clearing fast search switch(selection) { case "1": hide_inactive(1); break; case "2": hide_inactive(2); break; case "3": hide_inactive(3); break; case "7": hide_inactive(7); break; case "14": hide_inactive(14); break; case "21": hide_inactive(21); break; case "30": hide_inactive(30); break; case "60": hide_inactive(60); break; case "90": hide_inactive(90); break; case "120": hide_inactive(120); break; case "150": hide_inactive(150); break; case "all": hide_inactive(99999); break; case "all_top": hide_inactive(99999); break; case "unagreed": show_unvoted({'New':1,'Open':1,'Estimate':1}, 1); break; case "unaccepted": show_unvoted({'Done':1},2); break; case "unprioritized": show_unvoted({'New':1,'Open':1,'Estimate':1},3); break; case "unestimated": show_unvoted({'New':1,'Open':1,'Estimate':1},4); break; case "prioritized": show_voted({'New':1,'Open':1,'Estimate':1,'Done':1},3,1); break; case "added_by_me": show_added_by_me(); break; case "touched_by_me": show_hide_touched(true); break; case "untouched_by_me": show_hide_touched(false); break; default: show_tag(selection); break; } if (selection != "all" && selection != "all_top"){ $('#filtered_message').show(); $('#filter_detail').html($("#filter_select :selected").text()); } else { $('#filtered_message').hide(); } update_panel_counts(); } //Shows all items added by current user function show_added_by_me(){ for(var i = 0; i < D.length; i++ ){ if (D[i].author_id == currentUserId){ $("#item_" + i).show(); } else{ $("#item_" + i).hide(); } } } //Shows (or hides) all items touched by current user, if show is true it shows them, else it hides them function show_hide_touched(show){ for(var i = 0; i < D.length; i++ ){ if (is_item_touched_by_user(D[i])){ show ? $("#item_" + i).show() : $("#item_" + i).hide(); } else{ show ? $("#item_" + i).hide() : $("#item_" + i).show(); } } } //Hides all items except those with the tag function show_tag(tag){ for(var i = 0; i < D.length; i++ ){ if (D[i].tags_copy == null){ $("#item_" + i).hide(); } else if (D[i].tags_copy.indexOf(tag) != -1){ $("#item_" + i).show(); } else{ $("#item_" + i).hide(); } } } //Hides all items not active in the last *days* function hide_inactive(days){ var today = new Date(); for(var i = 0; i < D.length; i++ ){ if (new Date(D[i].updated_at) < new Date().setDate(today.getDate()-days)){ $("#item_" + i).hide(); } else{ $("#item_" + i).show(); } } } //Shows only items that don't have vote_type by current user for statuses defined in statuses option array function show_unvoted(statuses,vote_type){ for(var i = 0; i < D.length; i++ ){ if (statuses[D[i].status.name] == undefined){ $("#item_" + i).hide(); } else if (has_vote_type(D[i],vote_type)){ $("#item_" + i).hide(); } else{ $("#item_" + i).show(); } } } //Shows only items that have vote_type by current user for statuses defined in statuses option array function show_voted(statuses,vote_type,vote_value){ for(var i = 0; i < D.length; i++ ){ if (statuses[D[i].status.name] == undefined){ $("#item_" + i).hide(); } else if (has_vote_type(D[i],vote_type,vote_value)){ $("#item_" + i).show(); } else{ $("#item_" + i).hide(); } } } //If vote_value is defined, function checks that item has been voted on by curent user with vote_value, otherwise, it just checks that it was voted on by current user function has_vote_type(item,vote_type,vote_value){ for(var i = 0; i < item.issue_votes.length ; i++ ){ if (item.issue_votes[i].user_id != currentUserId){continue;} if (vote_value == undefined){ if (item.issue_votes[i].vote_type == vote_type){return true;} } else{ if (item.issue_votes[i].vote_type == vote_type && item.issue_votes[i].points == vote_value){return true;} } } return false; } //returns true if user has voted in any way on item function is_item_touched_by_user(item){ if (item.author_id == currentUserId){ return true; } for(var i = 0; i < item.issue_votes.length ; i++ ){ if (item.issue_votes[i].user_id == currentUserId){return true;} } for(var j = 0; j < item.journals.length ; j++ ){ if (item.journals[j].user_id == currentUserId){return true;} } return false; } function search_for(text){ text = text.toLowerCase(); if (text.length > 1){ for(var i = 0; i < D.length; i++ ) { if ((text.length == 0) || (D[i].subject.toLowerCase().indexOf(text) > -1)) { $("#item_" + i).show().removeHighlight(); if (text.length > 0){ $("#item_content_details_" + i).texthighlight(text); } } else if (D[i].description.toLowerCase().indexOf(text) > -1) { $("#item_" + i).show().removeHighlight(); } else if (D[i].tags_copy != null && D[i].tags_copy.toLowerCase().indexOf(text) > -1) { $("#item_" + i).show().removeHighlight(); } else { $("#item_" + i).hide().removeHighlight(); } if (String(D[i].id) == text){ $("#item_" + i).show(); } } } if ((text.length == 1)&&($('#filtered_message').is(":visible"))){ for(var x = 0; x < D.length; x++ ) { $("#item_" + x).show().removeHighlight(); } } if (text.length > 0){ $('#filtered_message').show(); $('#filter_detail').html(' "' + text + '"'); update_panel_counts(); } else{ clear_filters(); } } function search_for_old(text){ text = text.toLowerCase(); for(var i = 0; i < D.length; i++ ) { var subject = D[i].subject.toLowerCase(); if ((text.length == 0) || (subject.indexOf(text) > -1)) { $("#item_" + i).show().removeHighlight(); if (text.length > 0){ $("#item_content_details_" + i).texthighlight(text); } } else { $("#item_" + i).hide().removeHighlight(); } } if (text.length > 0){ $('#filtered_message').show(); $('#filter_detail').html(' "' + text + '"'); update_panel_counts(); } else{ clear_filters(); } } function clear_filters(){ $('#filtered_message').hide(); $('#fast_search').val(''); $('#fast_search').val(''); //clearing fast search $("#filter_select option[value='all_top']").attr('selected', 'selected'); //clearing select hide_inactive(99999); update_panel_counts(); for(var i = 0; i < D.length; i++ ) { $("#item_" + i).show().removeHighlight(); } } function send_item_action(dataId,action,extradata){ //Login required if (!is_user_logged_in()){return;} var data = "commit=Create&lock_version=" + D[dataId].lock_version + extradata; var url = url_for({ controller: 'issues', action : action, id : D[dataId].id }); $("#item_content_icons_editButton_" + dataId).remove(); $("#icon_set_" + dataId).addClass('updating'); $.ajax({ type: "POST", dataType: "json", url: url, data: data, success: function(html){ item_actioned(html,dataId,action); }, error: function (XMLHttpRequest, textStatus, errorThrown) { // typically only one of textStatus or errorThrown will have info // possible valuees for textstatus "timeout", "error", "notmodified" and "parsererror handle_error(XMLHttpRequest, textStatus, errorThrown, dataId,action); }, timeout: 30000 //30 seconds }); $('.bubbletip').hide(); } //returns true if item has a description or any journals that have notes function show_comment(item){ for(var i = 0; i < item.journals.length; i++ ){ if (item.journals[i].notes != '' && item.journals[i].notes != null){ return true; } } return false; } //resize heights of container and panels function resize(){ panel_height = $(window).height() - $('.gt-hd').height() - $('#help_section:visible').height() + 28;// + $('.gt-footer').height() ; // panel_height = $(window).height() - $('.gt-hd').height() + 28;// + $('.gt-footer').height() ; // $("#content").height(panel_height - 35); $(".list").height(panel_height - 75); $("#panels").show(); recalculate_widths(); } function insert_panel(position, name, title, visible){ var button_style = ""; if (visible){button_style = 'style="display:none;"';} generate_and_append_panel(position,name,title, visible); // $('#panel_buttons').prepend(''); var button = ""; button = button + ''; button = button + '
    ' + title + ' (0)
    '; button = button + '
    '; $('#panel_buttons').prepend(button); $("#help_image_panel_" + name).mybubbletip('#help_panel_' + name, {deltaDirection: 'right', bindShow: 'click'}); } function generate_and_append_panel(position,name,title, visible){ var panel_style = null; if (!visible){panel_style = 'style="display:none;"';} var panelHtml = ''; panelHtml = panelHtml + " "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + " "; panelHtml = panelHtml + " " + title + " (0)"; panelHtml = panelHtml + ' '; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + "
    "; panelHtml = panelHtml + ""; $("#main_row").append(panelHtml); $("#content").height(panel_height - 35); $(".list").height(panel_height - 75); } function update_panel_counts(){ update_panel_count('new'); update_panel_count('open'); update_panel_count('inprogress'); update_panel_count('done'); update_panel_count('canceled'); // update_panel_count('archived'); adjust_button_container_widths(); } function update_panel_count(name, skip_button){ try{ if ($("#" + name + '_panel').is(":visible")){ count = $("#" + name + "_start_of_list > *:visible").length; } else{ count = $("#" + name + "_start_of_list > *").length; } $("#" + name + '_panel_title').html($("#" + name + '_panel_title').html().replace(/\([0-9]*\)/,"(" + count + ")")); if (!skip_button){ $("#" + name + '_panel_toggle_count').html($("#" + name + '_panel_toggle_count').html().replace(/\([0-9]*\)/,"(" + count + ")")); } return true; } catch(err){ return false; } } function close_panel(name){ $('#' + name + '_panel').hide(); $('#' + name + '_panel_toggle').show(); recalculate_widths(); if (name == "new"){keyboard_shortcuts = true;} //If we're closing the new panel, then we want keyboard shortcuts to be on again, in case they were off if (name.indexOf("retro") == 0){ //If we're closing a retrospective panel, we untoggle the expander button retroId = name.split('_')[1]; $('#done_itemList_' + retroId + '_toggle_expanded_button').attr('src','/images/iteration_expander_closed.png'); } } function show_panel(name){ $('#' + name + '_panel').show(); $('#' + name + '_panel_toggle').hide(); recalculate_widths(); $('#' + name + '_close').addClass('closePanel').removeClass('closePanelLoading'); } // Sorts items in a plane by priority (highest first) followed by created date (oldest first) function sort_panel(name){ var listitems = $('#' + name + '_start_of_list').children().get(); //Setting highest_pri to priority of first item in open panel // if ((name == "open") && (listitems.length > 0)){ // var first_item_id = listitems[0].id.replace(/item_/g,''); // // var first_item_data_id = ITEMHASH["item" + first_item_id]; // //BUGBUG: listitems.length could be greater than 0 if it has only one item that's being edited // highest_pri = D[first_item_id].pri; // } listitems.sort(function(a, b) { var compA = a.id.replace(/item_/g,''); var compB = b.id.replace(/item_/g,''); // if (name == "open"){ // if (D[compA].pri > highest_pri) { highest_pri = D[compA].pri;} // if (D[compB].pri > highest_pri) { highest_pri = D[compB].pri;} // } if (compA == 'new_link') { return -1; } else if (compB == 'new_link') { return 1; } else if (D[compA].agree_total + D[compA].agree_total_nonbind > D[compB].agree_total + D[compB].agree_total_nonbind) { return -1; } else if (D[compA].agree_total + D[compA].agree_total_nonbind < D[compB].agree_total + D[compB].agree_total_nonbind) { return 1; } else if (new Date(D[compA].created_at) > new Date(D[compB].created_at)) { return 1; } else { return -1; } }); $('#' + name + '_start_of_list').children().remove(); $.each(listitems, function() { $('#' + name + '_start_of_list').append(this); }); // if (name == "open"){ // show_start_buttons(); // } } // function show_start_buttons(){ // $(".action_button_start").hide(); // for (var i=0; i < startable_priority_tiers; i++){ // $(".pri_" + (highest_pri - i)).find(".action_button_start").show(); // } // $(".points_0").find(".action_button_start").show(); // } function recalculate_widths(){ var new_width = $('#main').width() / $('.panel:visible').length; $('.panel:visible').width(new_width); adjust_button_container_widths(); } function expand_item(dataId){ $('#item_' + dataId).replaceWith(generate_item_edit(dataId)); //arming file upload $('#file_upload_' + dataId).fileUploadUI({ uploadTable: $('#files_' + dataId), downloadTable: $('#files_' + dataId), buildUploadRow: function (files, index) { return $('' + files[index].name + '<\/td>' + '
    <\/div><\/td>' + '' + '' button_data = ["TextileEditor.buttons.push(\"%s\");" % escape_javascript(b)] actual = textile_editor_button('Hello', :id => 'test_button', :onclick => "alert('Hello!')", :title => 'Hello world' ) assert_equal button_data, actual create_extended_editor('article', 'body') output = textile_editor_initialize() assert_equal expected_initialize_output(:prototype, [ ['article_body', 'extended'] ], button_data), output end def test_textile_extract_dom_ids_works_with_arrayed_hash hash_with_array = { :recipe => [ :instructions, :introduction ] } assert_equal [ 'recipe_instructions', 'recipe_introduction' ], textile_extract_dom_ids(hash_with_array) end def test_textile_extract_dom_ids_works_with_hash hash_with_symbol = { :story => :title } assert_equal [ 'story_title' ], textile_extract_dom_ids(hash_with_symbol) end def test_textile_extract_dom_ids_works_with_ids straight_id = 'article_page' assert_equal [ 'article_page' ], textile_extract_dom_ids(straight_id) end def test_textile_extract_dom_ids_works_with_mixed_params paramd = %w(article_page) paramd += [{ :recipe => [ :instructions, :introduction ], :story => :title }] assert_equal %w(article_page recipe_instructions recipe_introduction story_title).sort, textile_extract_dom_ids(*paramd).sort { |a,b| a.to_s <=> b.to_s } end def test_textile_editor_button b = '' expected = ['TextileEditor.buttons.push("%s");' % escape_javascript(b)] actual = textile_editor_button('Hello', :id => 'test_button', :onclick => "alert('Hello!')", :title => 'Hello world' ) assert_equal expected, actual end end ================================================ FILE: vendor/plugins/yaml_db/README ================================================ = YamlDb YamlDb is a database-independent format for dumping and restoring data. It complements the the database-independent schema format found in db/schema.rb. The data is saved into db/data.yml. This can be used as a replacement for mysqldump or pg_dump, but only for the databases typically used by Rails apps. Users, permissions, schemas, triggers, and other advanced database features are not supported - by design. Any database that has an ActiveRecord adapter should work. == Usage rake db:data:dump -> Dump contents of Rails database to db/data.yml rake db:data:load -> Load contents of db/data.yml into the database Further, there are tasks db:dump and db:load which do the entire database (the equivalent of running db:schema:dump followed by db:data:load). == Examples One common use would be to switch your data from one database backend to another. For example, let's say you wanted to switch from SQLite to MySQL. You might execute the following steps: 1. rake db:dump 2. Edit config/database.yml and change your adapter to mysql, set up database params 3. mysqladmin create [database name] 4. rake db:load == Credits Created by Orion Henry and Adam Wiggins. Major updates by Ricardo Chimal, Jr. Patches contributed by Michael Irwin, Tom Locke, and Tim Galeckas. Send questions, feedback, or patches to the Heroku mailing list: http://groups.google.com/group/heroku ================================================ FILE: vendor/plugins/yaml_db/Rakefile ================================================ require 'rake' require 'spec/rake/spectask' desc "Run all specs" Spec::Rake::SpecTask.new('spec') do |t| t.spec_files = FileList['spec/*_spec.rb'] end task :default => :spec ================================================ FILE: vendor/plugins/yaml_db/about.yml ================================================ author: Orion Henry and Adam Wiggins of Heroku summary: Dumps and loads a database-independent data dump format in db/data.yml. homepage: http://opensource.heroku.com/ license: MIT rails_version: 1.2+ ================================================ FILE: vendor/plugins/yaml_db/init.rb ================================================ require 'yaml_db' ================================================ FILE: vendor/plugins/yaml_db/lib/tasks/yaml_db_tasks.rake ================================================ namespace :db do desc "Dump schema and data to db/schema.rb and db/data.yml" task(:dump => [ "db:schema:dump", "db:data:dump" ]) desc "Load schema and data from db/schema.rb and db/data.yml" task(:load => [ "db:schema:load", "db:data:load" ]) namespace :data do def db_dump_data_file "#{RAILS_ROOT}/db/data.yml" end desc "Dump contents of database to db/data.yml" task(:dump => :environment) do YamlDb.dump db_dump_data_file end desc "Load contents of db/data.yml into database" task(:load => :environment) do YamlDb.load db_dump_data_file end end end ================================================ FILE: vendor/plugins/yaml_db/lib/yaml_db.rb ================================================ require 'rubygems' require 'yaml' require 'active_record' module YamlDb def self.dump(filename) disable_logger YamlDb::Dump.dump(File.new(filename, "w")) reenable_logger end def self.load(filename) disable_logger YamlDb::Load.load(File.new(filename, "r")) reenable_logger end def self.disable_logger @@old_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil end def self.reenable_logger ActiveRecord::Base.logger = @@old_logger end end module YamlDb::Utils def self.chunk_records(records) yaml = [ records ].to_yaml yaml.sub!("--- \n", "") yaml.sub!('- - -', ' - -') yaml end def self.unhash(hash, keys) keys.map { |key| hash[key] } end def self.unhash_records(records, keys) records.each_with_index do |record, index| records[index] = unhash(record, keys) end records end def self.convert_booleans(records, columns) records.each do |record| columns.each do |column| next if is_boolean(record[column]) record[column] = (record[column] == 't' or record[column] == '1') end end records end def self.boolean_columns(table) columns = ActiveRecord::Base.connection.columns(table).reject { |c| c.type != :boolean } columns.map { |c| c.name } end def self.is_boolean(value) value.kind_of?(TrueClass) or value.kind_of?(FalseClass) end def self.quote_table(table) ActiveRecord::Base.connection.quote_table_name(table) end end module YamlDb::Dump def self.dump(io) tables.each do |table| dump_table(io, table) end end def self.tables ActiveRecord::Base.connection.tables.reject { |table| ['schema_info', 'schema_migrations'].include?(table) } end def self.dump_table(io, table) return if table_record_count(table).zero? dump_table_columns(io, table) dump_table_records(io, table) end def self.dump_table_columns(io, table) io.write("\n") io.write({ table => { 'columns' => table_column_names(table) } }.to_yaml) end def self.dump_table_records(io, table) table_record_header(io) column_names = table_column_names(table) each_table_page(table) do |records| rows = YamlDb::Utils.unhash_records(records, column_names) io.write(YamlDb::Utils.chunk_records(records)) end end def self.table_record_header(io) io.write(" records: \n") end def self.table_column_names(table) ActiveRecord::Base.connection.columns(table).map { |c| c.name } end def self.each_table_page(table, records_per_page=1000) total_count = table_record_count(table) pages = (total_count.to_f / records_per_page).ceil - 1 id = table_column_names(table).first boolean_columns = YamlDb::Utils.boolean_columns(table) quoted_table_name = YamlDb::Utils.quote_table(table) (0..pages).to_a.each do |page| sql = ActiveRecord::Base.connection.add_limit_offset!("SELECT * FROM #{quoted_table_name} ORDER BY #{id}", :limit => records_per_page, :offset => records_per_page * page ) records = ActiveRecord::Base.connection.select_all(sql) records = YamlDb::Utils.convert_booleans(records, boolean_columns) yield records end end def self.table_record_count(table) ActiveRecord::Base.connection.select_one("SELECT COUNT(*) FROM #{YamlDb::Utils.quote_table(table)}").values.first.to_i end end module YamlDb::Load def self.load(io) ActiveRecord::Base.connection.transaction do YAML.load_documents(io) do |ydoc| ydoc.keys.each do |table_name| next if ydoc[table_name].nil? load_table(table_name, ydoc[table_name]) end end end end def self.truncate_table(table) begin ActiveRecord::Base.connection.execute("TRUNCATE #{YamlDb::Utils.quote_table(table)}") rescue Exception ActiveRecord::Base.connection.execute("DELETE FROM #{YamlDb::Utils.quote_table(table)}") end end def self.load_table(table, data) column_names = data['columns'] truncate_table(table) load_records(table, column_names, data['records']) reset_pk_sequence!(table) end def self.load_records(table, column_names, records) quoted_column_names = column_names.map { |column| ActiveRecord::Base.connection.quote_column_name(column) }.join(',') quoted_table_name = YamlDb::Utils.quote_table(table) records.each do |record| ActiveRecord::Base.connection.execute("INSERT INTO #{quoted_table_name} (#{quoted_column_names}) VALUES (#{record.map { |r| ActiveRecord::Base.connection.quote(r) }.join(',')})") end end def self.reset_pk_sequence!(table_name) if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) ActiveRecord::Base.connection.reset_pk_sequence!(table_name) end end end ================================================ FILE: vendor/plugins/yaml_db/spec/base.rb ================================================ require 'rubygems' require 'spec' $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') require 'yaml_db' ================================================ FILE: vendor/plugins/yaml_db/spec/yaml_dump_spec.rb ================================================ require File.dirname(__FILE__) + '/base' describe YamlDb::Dump do before do File.stub!(:new).with('dump.yml', 'w').and_return(StringIO.new) ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) ActiveRecord::Base.connection = mock('connection') ActiveRecord::Base.connection.stub!(:tables).and_return([ 'mytable', 'schema_info', 'schema_migrations' ]) ActiveRecord::Base.connection.stub!(:columns).with('mytable').and_return([ mock('a',:name => 'a'), mock('b', :name => 'b') ]) ActiveRecord::Base.connection.stub!(:select_one).and_return({"count"=>"2"}) ActiveRecord::Base.connection.stub!(:select_all).and_return([ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ]) YamlDb::Utils.stub!(:quote_table).with('mytable').and_return('mytable') end before(:each) do @io = StringIO.new end it "should return a formatted string" do YamlDb::Dump.table_record_header(@io) @io.rewind @io.read.should == " records: \n" end it "should return a list of column names" do YamlDb::Dump.table_column_names('mytable').should == [ 'a', 'b' ] end it "should return a list of tables without the rails schema table" do YamlDb::Dump.tables.should == ['mytable'] end it "should return the total number of records in a table" do YamlDb::Dump.table_record_count('mytable').should == 2 end it "should return a yaml string that contains a table header and column names" do YamlDb::Dump.stub!(:table_column_names).with('mytable').and_return([ 'a', 'b' ]) YamlDb::Dump.dump_table_columns(@io, 'mytable') @io.rewind @io.read.should == < 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ] end end it "should paginate records from the database and return them" do ActiveRecord::Base.connection.stub!(:select_all).and_return([ { 'a' => 1, 'b' => 2 } ], [ { 'a' => 3, 'b' => 4 } ]) records = [ ] YamlDb::Dump.each_table_page('mytable', 1) do |page| page.size.should == 1 records.concat(page) end records.should == [ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ] end it "should return dump the records for a table in yaml to a given io stream" do YamlDb::Dump.dump_table_records(@io, 'mytable') @io.rewind @io.read.should == < true) ActiveRecord::Base.connection = mock('connection') ActiveRecord::Base.connection.stub!(:transaction).and_yield end before(:each) do @io = StringIO.new end it "should truncate the table" do ActiveRecord::Base.connection.stub!(:execute).with("TRUNCATE mytable").and_return(true) ActiveRecord::Base.connection.should_not_receive(:execute).with("DELETE FROM mytable") YamlDb::Load.truncate_table('mytable') end it "should delete the table if truncate throws an exception" do ActiveRecord::Base.connection.should_receive(:execute).with("TRUNCATE mytable").and_raise() ActiveRecord::Base.connection.should_receive(:execute).with("DELETE FROM mytable").and_return(true) YamlDb::Load.truncate_table('mytable') end it "should insert records into a table" do ActiveRecord::Base.connection.stub!(:quote_column_name).with('a').and_return('a') ActiveRecord::Base.connection.stub!(:quote_column_name).with('b').and_return('b') ActiveRecord::Base.connection.stub!(:quote).with(1).and_return("'1'") ActiveRecord::Base.connection.stub!(:quote).with(2).and_return("'2'") ActiveRecord::Base.connection.stub!(:quote).with(3).and_return("'3'") ActiveRecord::Base.connection.stub!(:quote).with(4).and_return("'4'") ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,b) VALUES ('1','2')") ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,b) VALUES ('3','4')") YamlDb::Load.load_records('mytable', ['a', 'b'], [[1, 2], [3, 4]]) end it "should quote column names that correspond to sql keywords" do ActiveRecord::Base.connection.stub!(:quote_column_name).with('a').and_return('a') ActiveRecord::Base.connection.stub!(:quote_column_name).with('count').and_return('"count"') ActiveRecord::Base.connection.stub!(:quote).with(1).and_return("'1'") ActiveRecord::Base.connection.stub!(:quote).with(2).and_return("'2'") ActiveRecord::Base.connection.stub!(:quote).with(3).and_return("'3'") ActiveRecord::Base.connection.stub!(:quote).with(4).and_return("'4'") ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,\"count\") VALUES ('1','2')") ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,\"count\") VALUES ('3','4')") YamlDb::Load.load_records('mytable', ['a', 'count'], [[1, 2], [3, 4]]) end it "should truncate the table and then load the records into the table" do YamlDb::Load.should_receive(:truncate_table).with('mytable') YamlDb::Load.should_receive(:load_records).with('mytable', ['a', 'b'], [[1, 2], [3, 4]]) YamlDb::Load.should_receive(:reset_pk_sequence!).with('mytable') YamlDb::Load.load_table('mytable', { 'columns' => [ 'a', 'b' ], 'records' => [[1, 2], [3, 4]] }) end it "should call load structure for each document in the file" do YAML.should_receive(:load_documents).with(@io).and_yield({ 'mytable' => { 'columns' => [ 'a', 'b' ], 'records' => [[1, 2], [3, 4]] } }) YamlDb::Load.should_receive(:load_table).with('mytable', { 'columns' => [ 'a', 'b' ], 'records' => [[1, 2], [3, 4]] }) YamlDb::Load.load(@io) end it "should not call load structure when the document in the file contains no records" do YAML.should_receive(:load_documents).with(@io).and_yield({ 'mytable' => nil }) YamlDb::Load.should_not_receive(:load_table) YamlDb::Load.load(@io) end it "should call reset pk sequence if the connection adapter is postgres" do ActiveRecord::Base.connection.should_receive(:respond_to?).with(:reset_pk_sequence!).and_return(true) ActiveRecord::Base.connection.should_receive(:reset_pk_sequence!).with('mytable') YamlDb::Load.reset_pk_sequence!('mytable') end it "should not call reset pk sequence for other adapters" do ActiveRecord::Base.connection.should_receive(:respond_to?).with(:reset_pk_sequence!).and_return(false) ActiveRecord::Base.connection.should_not_receive(:reset_pk_sequence!) YamlDb::Load.reset_pk_sequence!('mytable') end end ================================================ FILE: vendor/plugins/yaml_db/spec/yaml_utils_spec.rb ================================================ require File.dirname(__FILE__) + '/base' describe YamlDb::Utils, " convert records utility method" do before do ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) ActiveRecord::Base.connection = mock('connection') end it "turns an array with one record into a yaml chunk" do YamlDb::Utils.chunk_records([ %w(a b) ]).should == < 1, 'b' => 2 }, [ 'b', 'a' ]).should == [ 2, 1 ] end it "should unhash each hash an array using an array of ordered keys" do YamlDb::Utils.unhash_records([ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ], [ 'b', 'a' ]).should == [ [ 2, 1 ], [ 4, 3 ] ] end it "should return true if it is a boolean type" do YamlDb::Utils.is_boolean(true).should == true YamlDb::Utils.is_boolean('true').should_not == true end it "should return an array of boolean columns" do ActiveRecord::Base.connection.stub!(:columns).with('mytable').and_return([ mock('a',:name => 'a',:type => :string), mock('b', :name => 'b',:type => :boolean) ]) YamlDb::Utils.boolean_columns('mytable').should == ['b'] end it "should quote the table name" do ActiveRecord::Base.connection.should_receive(:quote_table_name).with('values').and_return('`values`') YamlDb::Utils.quote_table('values').should == '`values`' end end