From 400ba25516f5e6a39695022b407dd645d4203764 Mon Sep 17 00:00:00 2001 From: mbugeia Date: Sun, 23 Nov 2014 20:13:38 +0100 Subject: [PATCH] Update kanboard v1.0.9 --- sources/.gitignore | 57 - sources/.scrutinizer.yml | 53 - sources/.travis.yml | 10 - sources/README.markdown | 165 - sources/Vagrantfile | 29 - sources/app/Action/Base.php | 118 +- sources/app/Action/CommentCreation.php | 83 + .../app/Action/TaskAssignCategoryColor.php | 47 +- .../app/Action/TaskAssignCategoryLabel.php | 84 + .../app/Action/TaskAssignColorCategory.php | 47 +- sources/app/Action/TaskAssignColorUser.php | 48 +- sources/app/Action/TaskAssignCurrentUser.php | 57 +- sources/app/Action/TaskAssignSpecificUser.php | 48 +- sources/app/Action/TaskAssignUser.php | 81 + sources/app/Action/TaskClose.php | 69 +- sources/app/Action/TaskCreation.php | 81 + .../Action/TaskDuplicateAnotherProject.php | 45 +- sources/app/Action/TaskMoveAnotherProject.php | 45 +- sources/app/Action/TaskOpen.php | 73 + sources/app/Auth/Database.php | 5 +- sources/app/Auth/GitHub.php | 5 +- sources/app/Auth/Google.php | 5 +- sources/app/Auth/Ldap.php | 6 +- sources/app/Auth/RememberMe.php | 5 +- sources/app/Auth/ReverseProxy.php | 5 +- sources/app/Controller/Action.php | 74 +- sources/app/Controller/App.php | 18 +- sources/app/Controller/Base.php | 66 +- sources/app/Controller/Board.php | 106 +- sources/app/Controller/Category.php | 14 +- sources/app/Controller/Config.php | 133 +- sources/app/Controller/Project.php | 324 +- sources/app/Controller/Subtask.php | 34 +- sources/app/Controller/Task.php | 156 +- sources/app/Controller/User.php | 43 +- sources/app/Controller/Webhook.php | 65 + sources/app/Core/Event.php | 5 +- sources/app/Core/Listener.php | 4 +- sources/app/Core/Request.php | 86 +- sources/app/Core/Session.php | 16 +- sources/app/Core/Template.php | 2 +- sources/app/Core/Translator.php | 6 +- sources/app/Event/Base.php | 79 + .../app/Event/BaseNotificationListener.php | 87 - sources/app/Event/CommentHistoryListener.php | 73 - .../app/Event/CommentNotificationListener.php | 30 - .../app/Event/FileNotificationListener.php | 30 - sources/app/Event/NotificationListener.php | 83 + sources/app/Event/ProjectActivityListener.php | 61 + sources/app/Event/ProjectModificationDate.php | 63 - .../Event/ProjectModificationDateListener.php | 30 + .../app/Event/SubTaskNotificationListener.php | 30 - sources/app/Event/SubtaskHistoryListener.php | 73 - sources/app/Event/TaskHistoryListener.php | 63 - .../app/Event/TaskNotificationListener.php | 29 - sources/app/Event/WebhookListener.php | 32 +- sources/app/Locale/da_DK/translations.php | 561 +++ .../de_DE/translations.php | 309 +- .../es_ES/translations.php | 85 +- .../fi_FI/translations.php | 75 +- .../fr_FR/translations.php | 79 +- .../it_IT/translations.php | 185 +- sources/app/Locale/ja_JP/translations.php | 561 +++ .../pl_PL/translations.php | 75 +- .../pt_BR/translations.php | 75 +- .../ru_RU/translations.php | 97 +- .../sv_SE/translations.php | 251 +- sources/app/Locale/th_TH/translations.php | 561 +++ sources/app/Locale/zh_CN/translations.php | 561 +++ sources/app/Locales/zh_CN/translations.php | 506 --- sources/app/Model/Acl.php | 15 +- sources/app/Model/Action.php | 71 +- sources/app/Model/Authentication.php | 3 +- sources/app/Model/Base.php | 57 + sources/app/Model/BaseHistory.php | 70 - sources/app/Model/Board.php | 33 +- sources/app/Model/Color.php | 31 + sources/app/Model/CommentHistory.php | 152 - sources/app/Model/Config.php | 73 +- sources/app/Model/DateParser.php | 142 + sources/app/Model/GithubWebhook.php | 339 ++ sources/app/Model/Notification.php | 111 +- sources/app/Model/Project.php | 350 +- sources/app/Model/ProjectActivity.php | 206 ++ sources/app/Model/ProjectPermission.php | 295 ++ sources/app/Model/SubTask.php | 14 +- sources/app/Model/SubtaskHistory.php | 161 - sources/app/Model/Task.php | 792 +---- sources/app/Model/TaskExport.php | 136 + sources/app/Model/TaskFinder.php | 325 ++ sources/app/Model/TaskHistory.php | 160 - sources/app/Model/TaskPermission.php | 32 + sources/app/Model/TaskValidator.php | 216 ++ sources/app/Model/TimeTracking.php | 45 + sources/app/Model/User.php | 161 +- sources/app/Model/Webhook.php | 17 +- sources/app/Schema/Mysql.php | 93 +- sources/app/Schema/Postgres.php | 96 +- sources/app/Schema/Sqlite.php | 91 +- sources/app/Template/action_event.php | 22 + .../{Templates => Template}/action_index.php | 11 +- .../{Templates => Template}/action_params.php | 5 +- .../{Templates => Template}/action_remove.php | 4 +- .../{Templates => Template}/app_forbidden.php | 0 sources/app/Template/app_index.php | 45 + .../{Templates => Template}/app_notfound.php | 0 .../board_assignee.php | 0 .../board_category.php | 0 .../{Templates => Template}/board_edit.php | 6 +- sources/app/Template/board_index.php | 56 + .../{Templates => Template}/board_public.php | 0 .../{Templates => Template}/board_remove.php | 4 +- .../{Templates => Template}/board_show.php | 4 +- .../{Templates => Template}/board_task.php | 20 +- .../{Templates => Template}/category_edit.php | 0 .../category_index.php | 0 .../category_remove.php | 0 .../comment_create.php | 8 +- .../{Templates => Template}/comment_edit.php | 0 .../comment_forbidden.php | 0 .../comment_remove.php | 0 .../{Templates => Template}/comment_show.php | 15 +- sources/app/Template/config_about.php | 41 + sources/app/Template/config_api.php | 18 + sources/app/Template/config_application.php | 27 + sources/app/Template/config_board.php | 29 + sources/app/Template/config_layout.php | 13 + sources/app/Template/config_sidebar.php | 22 + sources/app/Template/config_webhook.php | 38 + .../event_comment_create.php | 8 +- .../event_comment_update.php | 8 +- sources/app/Template/event_subtask_create.php | 19 + sources/app/Template/event_subtask_update.php | 19 + .../Template/event_task_assignee_change.php | 12 + .../event_task_close.php | 2 +- .../event_task_create.php | 2 +- .../event_task_move_column.php | 4 +- .../event_task_move_position.php | 4 +- .../event_task_open.php | 2 +- .../event_task_update.php | 2 +- .../app/{Templates => Template}/file_new.php | 0 .../app/{Templates => Template}/file_open.php | 0 .../{Templates => Template}/file_remove.php | 0 sources/app/Template/file_show.php | 23 + .../app/{Templates => Template}/layout.php | 13 +- .../notification_comment_creation.php | 4 +- .../notification_comment_update.php | 4 +- .../notification_file_creation.php | 2 +- sources/app/Template/notification_footer.php | 6 + .../notification_subtask_creation.php | 2 +- .../notification_subtask_update.php | 2 +- .../notification_task_assignee_change.php | 4 +- .../notification_task_close.php | 2 +- .../notification_task_creation.php | 4 +- .../notification_task_due.php | 9 +- .../notification_task_move_column.php | 2 +- .../notification_task_move_position.php | 2 +- .../notification_task_open.php | 2 +- .../notification_task_update.php | 4 +- sources/app/Template/project_activity.php | 18 + sources/app/Template/project_disable.php | 14 + sources/app/Template/project_duplicate.php | 14 + .../{Templates => Template}/project_edit.php | 4 +- sources/app/Template/project_enable.php | 14 + sources/app/Template/project_events.php | 21 + .../project_export.php | 4 +- .../{Templates => Template}/project_feed.php | 4 +- .../{Templates => Template}/project_index.php | 20 +- .../project_layout.php | 6 +- .../{Templates => Template}/project_new.php | 9 +- sources/app/Template/project_remove.php | 14 + .../project_search.php | 15 +- sources/app/Template/project_share.php | 19 + .../{Templates => Template}/project_show.php | 25 +- .../project_sidebar.php | 22 +- .../{Templates => Template}/project_tasks.php | 7 +- sources/app/Template/project_users.php | 57 + .../subtask_create.php | 2 +- .../{Templates => Template}/subtask_edit.php | 4 +- .../subtask_remove.php | 0 sources/app/Template/subtask_show.php | 72 + .../{Templates => Template}/task_close.php | 0 sources/app/Template/task_comments.php | 29 + .../{Templates => Template}/task_details.php | 20 + .../task_duplicate.php | 0 .../task_duplicate_project.php | 0 sources/app/Template/task_edit.php | 51 + .../task_edit_description.php | 0 .../{Templates => Template}/task_layout.php | 2 +- .../task_move_project.php | 0 .../app/{Templates => Template}/task_new.php | 13 +- .../app/{Templates => Template}/task_open.php | 0 sources/app/Template/task_public.php | 27 + .../{Templates => Template}/task_remove.php | 0 sources/app/Template/task_show.php | 7 + .../app/Template/task_show_description.php | 24 + .../{Templates => Template}/task_sidebar.php | 4 +- .../{Templates => Template}/task_table.php | 24 +- sources/app/Template/task_time.php | 15 + sources/app/Template/task_timesheet.php | 13 + .../app/{Templates => Template}/user_edit.php | 0 .../{Templates => Template}/user_external.php | 4 +- .../{Templates => Template}/user_index.php | 26 +- .../app/{Templates => Template}/user_last.php | 0 .../{Templates => Template}/user_layout.php | 6 +- .../{Templates => Template}/user_login.php | 2 +- .../app/{Templates => Template}/user_new.php | 0 .../user_notifications.php | 0 .../{Templates => Template}/user_password.php | 0 .../{Templates => Template}/user_remove.php | 0 .../{Templates => Template}/user_sessions.php | 0 .../app/{Templates => Template}/user_show.php | 2 +- .../{Templates => Template}/user_sidebar.php | 0 sources/app/Templates/board_index.php | 32 - sources/app/Templates/config_index.php | 71 - .../app/Templates/event_subtask_create.php | 12 - .../app/Templates/event_subtask_update.php | 12 - .../Templates/event_task_assignee_change.php | 6 - sources/app/Templates/file_show.php | 17 - sources/app/Templates/notification_footer.php | 6 - sources/app/Templates/project_activity.php | 37 - sources/app/Templates/project_disable.php | 14 - sources/app/Templates/project_duplicate.php | 14 - sources/app/Templates/project_enable.php | 14 - sources/app/Templates/project_remove.php | 14 - sources/app/Templates/project_share.php | 21 - sources/app/Templates/project_users.php | 36 - sources/app/Templates/subtask_show.php | 70 - sources/app/Templates/task_comments.php | 15 - sources/app/Templates/task_edit.php | 58 - sources/app/Templates/task_public.php | 11 - sources/app/Templates/task_show.php | 14 - .../app/Templates/task_show_description.php | 11 - sources/app/constants.php | 14 +- sources/app/functions.php | 146 +- sources/app/helpers.php | 436 ++- sources/assets/css/app.css | 141 +- sources/assets/js/app.js | 40 +- sources/config.default.php | 15 +- sources/docs/.htaccess | 1 - sources/docs/api-json-rpc.markdown | 1999 ----------- sources/docs/automatic-actions.markdown | 97 - sources/docs/board-configuration.markdown | 30 - sources/docs/centos-installation.markdown | 69 - sources/docs/cli.markdown | 54 - sources/docs/coding-standards.markdown | 23 - sources/docs/creating-projects.markdown | 14 - sources/docs/creating-tasks.markdown | 24 - sources/docs/debian-installation.markdown | 42 - sources/docs/editing-projects.markdown | 8 - sources/docs/email-configuration.markdown | 112 - sources/docs/github-authentication.markdown | 62 - sources/docs/google-authentication.markdown | 65 - sources/docs/installation.markdown | 33 - sources/docs/ldap-authentication.markdown | 194 - sources/docs/manage-users.markdown | 45 - sources/docs/mysql-configuration.markdown | 55 - sources/docs/nginx-ssl-php-fpm.markdown | 238 -- .../docs/postgresql-configuration.markdown | 38 - .../reverse-proxy-authentication.markdown | 50 - sources/docs/sharing-projects.markdown | 31 - sources/docs/sqlite-database.markdown | 50 - sources/docs/syntax-guide.markdown | 139 - sources/docs/tests.markdown | 180 - sources/docs/translations.markdown | 28 - sources/docs/ubuntu-installation.markdown | 28 - sources/docs/update.markdown | 19 - sources/docs/usage-examples.markdown | 67 - sources/docs/vagrant.markdown | 8 - sources/docs/webhooks.markdown | 98 - .../docs/windows-iis-installation.markdown | 46 - sources/jsonrpc.php | 43 +- sources/kanboard | 18 +- sources/scripts/.htaccess | 1 - sources/scripts/api-client.php | 20 - sources/scripts/create-random-tasks.php | 26 - sources/scripts/make-archive.sh | 29 - sources/scripts/make-tag.sh | 6 - sources/scripts/sync-locales.php | 43 - sources/tests/.htaccess | 1 - sources/tests/functionals.mysql.xml | 16 - sources/tests/functionals.postgres.xml | 16 - sources/tests/functionals.sqlite.xml | 13 - sources/tests/functionals/ApiTest.php | 559 --- sources/tests/units.mysql.xml | 11 - sources/tests/units.postgres.xml | 12 - sources/tests/units.sqlite.xml | 11 - sources/tests/units/AclTest.php | 112 - .../ActionTaskAssignColorCategoryTest.php | 76 - .../units/ActionTaskAssignColorUserTest.php | 70 - .../units/ActionTaskAssignCurrentUserTest.php | 73 - .../ActionTaskAssignSpecificUserTest.php | 66 - sources/tests/units/ActionTaskCloseTest.php | 65 - .../ActionTaskDuplicateAnotherProjectTest.php | 89 - .../ActionTaskMoveAnotherProjectTest.php | 84 - sources/tests/units/ActionTest.php | 256 -- sources/tests/units/Base.php | 48 - sources/tests/units/BoardTest.php | 195 - sources/tests/units/CategoryTest.php | 57 - sources/tests/units/CommentTest.php | 133 - sources/tests/units/ConfigTest.php | 32 - sources/tests/units/ProjectTest.php | 281 -- sources/tests/units/SubtaskTest.php | 56 - sources/tests/units/TaskHistoryTest.php | 98 - sources/tests/units/TaskTest.php | 676 ---- sources/tests/units/UserTest.php | 82 - sources/vendor/JsonRPC/Client.php | 7 +- sources/vendor/Michelf/Markdown.inc.php | 10 - sources/vendor/Michelf/Markdown.php | 3144 ----------------- sources/vendor/Michelf/MarkdownExtra.inc.php | 11 - sources/vendor/Michelf/MarkdownExtra.php | 38 - .../vendor/Michelf/MarkdownInterface.inc.php | 9 - sources/vendor/Michelf/MarkdownInterface.php | 34 - sources/vendor/Parsedown/LICENSE.txt | 20 + sources/vendor/Parsedown/Parsedown.php | 1423 ++++++++ sources/vendor/PicoDb/Database.php | 5 + sources/vendor/PicoDb/Drivers/Mysql.php | 1 - sources/vendor/PicoDb/Drivers/Sqlite.php | 1 - sources/vendor/PicoDb/Table.php | 8 +- 319 files changed, 10599 insertions(+), 15353 deletions(-) delete mode 100644 sources/.gitignore delete mode 100644 sources/.scrutinizer.yml delete mode 100644 sources/.travis.yml delete mode 100644 sources/README.markdown delete mode 100644 sources/Vagrantfile create mode 100644 sources/app/Action/CommentCreation.php create mode 100644 sources/app/Action/TaskAssignCategoryLabel.php create mode 100644 sources/app/Action/TaskAssignUser.php create mode 100644 sources/app/Action/TaskCreation.php create mode 100644 sources/app/Action/TaskOpen.php create mode 100644 sources/app/Controller/Webhook.php create mode 100644 sources/app/Event/Base.php delete mode 100644 sources/app/Event/BaseNotificationListener.php delete mode 100644 sources/app/Event/CommentHistoryListener.php delete mode 100644 sources/app/Event/CommentNotificationListener.php delete mode 100644 sources/app/Event/FileNotificationListener.php create mode 100644 sources/app/Event/NotificationListener.php create mode 100644 sources/app/Event/ProjectActivityListener.php delete mode 100644 sources/app/Event/ProjectModificationDate.php create mode 100644 sources/app/Event/ProjectModificationDateListener.php delete mode 100644 sources/app/Event/SubTaskNotificationListener.php delete mode 100644 sources/app/Event/SubtaskHistoryListener.php delete mode 100644 sources/app/Event/TaskHistoryListener.php delete mode 100644 sources/app/Event/TaskNotificationListener.php create mode 100644 sources/app/Locale/da_DK/translations.php rename sources/app/{Locales => Locale}/de_DE/translations.php (62%) rename sources/app/{Locales => Locale}/es_ES/translations.php (90%) rename sources/app/{Locales => Locale}/fi_FI/translations.php (90%) rename sources/app/{Locales => Locale}/fr_FR/translations.php (87%) rename sources/app/{Locales => Locale}/it_IT/translations.php (80%) create mode 100644 sources/app/Locale/ja_JP/translations.php rename sources/app/{Locales => Locale}/pl_PL/translations.php (90%) rename sources/app/{Locales => Locale}/pt_BR/translations.php (90%) rename sources/app/{Locales => Locale}/ru_RU/translations.php (86%) rename sources/app/{Locales => Locale}/sv_SE/translations.php (67%) create mode 100644 sources/app/Locale/th_TH/translations.php create mode 100644 sources/app/Locale/zh_CN/translations.php delete mode 100644 sources/app/Locales/zh_CN/translations.php delete mode 100644 sources/app/Model/BaseHistory.php create mode 100644 sources/app/Model/Color.php delete mode 100644 sources/app/Model/CommentHistory.php create mode 100644 sources/app/Model/DateParser.php create mode 100644 sources/app/Model/GithubWebhook.php create mode 100644 sources/app/Model/ProjectActivity.php create mode 100644 sources/app/Model/ProjectPermission.php delete mode 100644 sources/app/Model/SubtaskHistory.php create mode 100644 sources/app/Model/TaskExport.php create mode 100644 sources/app/Model/TaskFinder.php delete mode 100644 sources/app/Model/TaskHistory.php create mode 100644 sources/app/Model/TaskPermission.php create mode 100644 sources/app/Model/TaskValidator.php create mode 100644 sources/app/Model/TimeTracking.php create mode 100644 sources/app/Template/action_event.php rename sources/app/{Templates => Template}/action_index.php (86%) rename sources/app/{Templates => Template}/action_params.php (90%) rename sources/app/{Templates => Template}/action_remove.php (56%) rename sources/app/{Templates => Template}/app_forbidden.php (100%) create mode 100644 sources/app/Template/app_index.php rename sources/app/{Templates => Template}/app_notfound.php (100%) rename sources/app/{Templates => Template}/board_assignee.php (100%) rename sources/app/{Templates => Template}/board_category.php (100%) rename sources/app/{Templates => Template}/board_edit.php (79%) create mode 100644 sources/app/Template/board_index.php rename sources/app/{Templates => Template}/board_public.php (100%) rename sources/app/{Templates => Template}/board_remove.php (54%) rename sources/app/{Templates => Template}/board_show.php (89%) rename sources/app/{Templates => Template}/board_task.php (89%) rename sources/app/{Templates => Template}/category_edit.php (100%) rename sources/app/{Templates => Template}/category_index.php (100%) rename sources/app/{Templates => Template}/category_remove.php (100%) rename sources/app/{Templates => Template}/comment_create.php (67%) rename sources/app/{Templates => Template}/comment_edit.php (100%) rename sources/app/{Templates => Template}/comment_forbidden.php (100%) rename sources/app/{Templates => Template}/comment_remove.php (100%) rename sources/app/{Templates => Template}/comment_show.php (68%) create mode 100644 sources/app/Template/config_about.php create mode 100644 sources/app/Template/config_api.php create mode 100644 sources/app/Template/config_application.php create mode 100644 sources/app/Template/config_board.php create mode 100644 sources/app/Template/config_layout.php create mode 100644 sources/app/Template/config_sidebar.php create mode 100644 sources/app/Template/config_webhook.php rename sources/app/{Templates => Template}/event_comment_create.php (51%) rename sources/app/{Templates => Template}/event_comment_update.php (52%) create mode 100644 sources/app/Template/event_subtask_create.php create mode 100644 sources/app/Template/event_subtask_update.php create mode 100644 sources/app/Template/event_task_assignee_change.php rename sources/app/{Templates => Template}/event_task_close.php (79%) rename sources/app/{Templates => Template}/event_task_create.php (79%) rename sources/app/{Templates => Template}/event_task_move_column.php (60%) rename sources/app/{Templates => Template}/event_task_move_position.php (53%) rename sources/app/{Templates => Template}/event_task_open.php (79%) rename sources/app/{Templates => Template}/event_task_update.php (79%) rename sources/app/{Templates => Template}/file_new.php (100%) rename sources/app/{Templates => Template}/file_open.php (100%) rename sources/app/{Templates => Template}/file_remove.php (100%) create mode 100644 sources/app/Template/file_show.php rename sources/app/{Templates => Template}/layout.php (87%) rename sources/app/{Templates => Template}/notification_comment_creation.php (71%) rename sources/app/{Templates => Template}/notification_comment_update.php (66%) rename sources/app/{Templates => Template}/notification_file_creation.php (81%) create mode 100644 sources/app/Template/notification_footer.php rename sources/app/{Templates => Template}/notification_subtask_creation.php (93%) rename sources/app/{Templates => Template}/notification_subtask_update.php (95%) rename sources/app/{Templates => Template}/notification_task_assignee_change.php (80%) rename sources/app/{Templates => Template}/notification_task_close.php (81%) rename sources/app/{Templates => Template}/notification_task_creation.php (93%) rename sources/app/{Templates => Template}/notification_task_due.php (57%) rename sources/app/{Templates => Template}/notification_task_move_column.php (88%) rename sources/app/{Templates => Template}/notification_task_move_position.php (88%) rename sources/app/{Templates => Template}/notification_task_open.php (81%) rename sources/app/{Templates => Template}/notification_task_update.php (91%) create mode 100644 sources/app/Template/project_activity.php create mode 100644 sources/app/Template/project_disable.php create mode 100644 sources/app/Template/project_duplicate.php rename sources/app/{Templates => Template}/project_edit.php (60%) create mode 100644 sources/app/Template/project_enable.php create mode 100644 sources/app/Template/project_events.php rename sources/app/{Templates => Template}/project_export.php (81%) rename sources/app/{Templates => Template}/project_feed.php (86%) rename sources/app/{Templates => Template}/project_index.php (50%) rename sources/app/{Templates => Template}/project_layout.php (54%) rename sources/app/{Templates => Template}/project_new.php (51%) create mode 100644 sources/app/Template/project_remove.php rename sources/app/{Templates => Template}/project_search.php (59%) create mode 100644 sources/app/Template/project_share.php rename sources/app/{Templates => Template}/project_show.php (51%) rename sources/app/{Templates => Template}/project_sidebar.php (62%) rename sources/app/{Templates => Template}/project_tasks.php (79%) create mode 100644 sources/app/Template/project_users.php rename sources/app/{Templates => Template}/subtask_create.php (94%) rename sources/app/{Templates => Template}/subtask_edit.php (90%) rename sources/app/{Templates => Template}/subtask_remove.php (100%) create mode 100644 sources/app/Template/subtask_show.php rename sources/app/{Templates => Template}/task_close.php (100%) create mode 100644 sources/app/Template/task_comments.php rename sources/app/{Templates => Template}/task_details.php (78%) rename sources/app/{Templates => Template}/task_duplicate.php (100%) rename sources/app/{Templates => Template}/task_duplicate_project.php (100%) create mode 100644 sources/app/Template/task_edit.php rename sources/app/{Templates => Template}/task_edit_description.php (100%) rename sources/app/{Templates => Template}/task_layout.php (89%) rename sources/app/{Templates => Template}/task_move_project.php (100%) rename sources/app/{Templates => Template}/task_new.php (78%) rename sources/app/{Templates => Template}/task_open.php (100%) create mode 100644 sources/app/Template/task_public.php rename sources/app/{Templates => Template}/task_remove.php (100%) create mode 100644 sources/app/Template/task_show.php create mode 100644 sources/app/Template/task_show_description.php rename sources/app/{Templates => Template}/task_sidebar.php (90%) rename sources/app/{Templates => Template}/task_table.php (62%) create mode 100644 sources/app/Template/task_time.php create mode 100644 sources/app/Template/task_timesheet.php rename sources/app/{Templates => Template}/user_edit.php (100%) rename sources/app/{Templates => Template}/user_external.php (96%) rename sources/app/{Templates => Template}/user_index.php (64%) rename sources/app/{Templates => Template}/user_last.php (100%) rename sources/app/{Templates => Template}/user_layout.php (74%) rename sources/app/{Templates => Template}/user_login.php (95%) rename sources/app/{Templates => Template}/user_new.php (100%) rename sources/app/{Templates => Template}/user_notifications.php (100%) rename sources/app/{Templates => Template}/user_password.php (100%) rename sources/app/{Templates => Template}/user_remove.php (100%) rename sources/app/{Templates => Template}/user_sessions.php (100%) rename sources/app/{Templates => Template}/user_show.php (97%) rename sources/app/{Templates => Template}/user_sidebar.php (100%) delete mode 100644 sources/app/Templates/board_index.php delete mode 100644 sources/app/Templates/config_index.php delete mode 100644 sources/app/Templates/event_subtask_create.php delete mode 100644 sources/app/Templates/event_subtask_update.php delete mode 100644 sources/app/Templates/event_task_assignee_change.php delete mode 100644 sources/app/Templates/file_show.php delete mode 100644 sources/app/Templates/notification_footer.php delete mode 100644 sources/app/Templates/project_activity.php delete mode 100644 sources/app/Templates/project_disable.php delete mode 100644 sources/app/Templates/project_duplicate.php delete mode 100644 sources/app/Templates/project_enable.php delete mode 100644 sources/app/Templates/project_remove.php delete mode 100644 sources/app/Templates/project_share.php delete mode 100644 sources/app/Templates/project_users.php delete mode 100644 sources/app/Templates/subtask_show.php delete mode 100644 sources/app/Templates/task_comments.php delete mode 100644 sources/app/Templates/task_edit.php delete mode 100644 sources/app/Templates/task_public.php delete mode 100644 sources/app/Templates/task_show.php delete mode 100644 sources/app/Templates/task_show_description.php delete mode 100644 sources/docs/.htaccess delete mode 100644 sources/docs/api-json-rpc.markdown delete mode 100644 sources/docs/automatic-actions.markdown delete mode 100644 sources/docs/board-configuration.markdown delete mode 100644 sources/docs/centos-installation.markdown delete mode 100644 sources/docs/cli.markdown delete mode 100644 sources/docs/coding-standards.markdown delete mode 100644 sources/docs/creating-projects.markdown delete mode 100644 sources/docs/creating-tasks.markdown delete mode 100644 sources/docs/debian-installation.markdown delete mode 100644 sources/docs/editing-projects.markdown delete mode 100644 sources/docs/email-configuration.markdown delete mode 100644 sources/docs/github-authentication.markdown delete mode 100644 sources/docs/google-authentication.markdown delete mode 100644 sources/docs/installation.markdown delete mode 100644 sources/docs/ldap-authentication.markdown delete mode 100644 sources/docs/manage-users.markdown delete mode 100644 sources/docs/mysql-configuration.markdown delete mode 100644 sources/docs/nginx-ssl-php-fpm.markdown delete mode 100644 sources/docs/postgresql-configuration.markdown delete mode 100644 sources/docs/reverse-proxy-authentication.markdown delete mode 100644 sources/docs/sharing-projects.markdown delete mode 100644 sources/docs/sqlite-database.markdown delete mode 100644 sources/docs/syntax-guide.markdown delete mode 100644 sources/docs/tests.markdown delete mode 100644 sources/docs/translations.markdown delete mode 100644 sources/docs/ubuntu-installation.markdown delete mode 100644 sources/docs/update.markdown delete mode 100644 sources/docs/usage-examples.markdown delete mode 100644 sources/docs/vagrant.markdown delete mode 100644 sources/docs/webhooks.markdown delete mode 100644 sources/docs/windows-iis-installation.markdown delete mode 100644 sources/scripts/.htaccess delete mode 100644 sources/scripts/api-client.php delete mode 100644 sources/scripts/create-random-tasks.php delete mode 100644 sources/scripts/make-archive.sh delete mode 100644 sources/scripts/make-tag.sh delete mode 100644 sources/scripts/sync-locales.php delete mode 100644 sources/tests/.htaccess delete mode 100644 sources/tests/functionals.mysql.xml delete mode 100644 sources/tests/functionals.postgres.xml delete mode 100644 sources/tests/functionals.sqlite.xml delete mode 100644 sources/tests/functionals/ApiTest.php delete mode 100644 sources/tests/units.mysql.xml delete mode 100644 sources/tests/units.postgres.xml delete mode 100644 sources/tests/units.sqlite.xml delete mode 100644 sources/tests/units/AclTest.php delete mode 100644 sources/tests/units/ActionTaskAssignColorCategoryTest.php delete mode 100644 sources/tests/units/ActionTaskAssignColorUserTest.php delete mode 100644 sources/tests/units/ActionTaskAssignCurrentUserTest.php delete mode 100644 sources/tests/units/ActionTaskAssignSpecificUserTest.php delete mode 100644 sources/tests/units/ActionTaskCloseTest.php delete mode 100644 sources/tests/units/ActionTaskDuplicateAnotherProjectTest.php delete mode 100644 sources/tests/units/ActionTaskMoveAnotherProjectTest.php delete mode 100644 sources/tests/units/ActionTest.php delete mode 100644 sources/tests/units/Base.php delete mode 100644 sources/tests/units/BoardTest.php delete mode 100644 sources/tests/units/CategoryTest.php delete mode 100644 sources/tests/units/CommentTest.php delete mode 100644 sources/tests/units/ConfigTest.php delete mode 100644 sources/tests/units/ProjectTest.php delete mode 100644 sources/tests/units/SubtaskTest.php delete mode 100644 sources/tests/units/TaskHistoryTest.php delete mode 100644 sources/tests/units/TaskTest.php delete mode 100644 sources/tests/units/UserTest.php delete mode 100644 sources/vendor/Michelf/Markdown.inc.php delete mode 100644 sources/vendor/Michelf/Markdown.php delete mode 100644 sources/vendor/Michelf/MarkdownExtra.inc.php delete mode 100644 sources/vendor/Michelf/MarkdownExtra.php delete mode 100644 sources/vendor/Michelf/MarkdownInterface.inc.php delete mode 100644 sources/vendor/Michelf/MarkdownInterface.php create mode 100644 sources/vendor/Parsedown/LICENSE.txt create mode 100644 sources/vendor/Parsedown/Parsedown.php diff --git a/sources/.gitignore b/sources/.gitignore deleted file mode 100644 index c72adf5..0000000 --- a/sources/.gitignore +++ /dev/null @@ -1,57 +0,0 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so -*.pyc - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite -*.sqlite-journal - -# IDE generated files # -###################### -.buildpath -.project -/.settings/ -.idea - -# OS generated files # -###################### -.DS_Store -ehthumbs.db -Icon? -Thumbs.db -*.swp -.*.swp -*~ -*.lock -*.out - -# Vagrant # -########### -.vagrant - -# App specific # -################ -#config.php -#data/files diff --git a/sources/.scrutinizer.yml b/sources/.scrutinizer.yml deleted file mode 100644 index 26d490b..0000000 --- a/sources/.scrutinizer.yml +++ /dev/null @@ -1,53 +0,0 @@ -filter: - excluded_paths: - - 'vendor/*' - - 'tests/*' - - 'app/Templates/*' - paths: { } -tools: - php_sim: - enabled: true - min_mass: 16 - filter: - excluded_paths: - - 'vendor/*' - - 'tests/*' - - 'app/Templates/*' - paths: { } - php_pdepend: - enabled: true - configuration_file: null - suffixes: - - php - excluded_dirs: { } - filter: - excluded_paths: - - 'vendor/*' - - 'tests/*' - - 'app/Templates/*' - paths: { } - php_analyzer: - enabled: true - extensions: - - php - dependency_paths: { } - filter: - excluded_paths: - - 'vendor/*' - - 'tests/*' - - 'app/Templates/*' - paths: { } - path_configs: { } - php_changetracking: - enabled: true - bug_patterns: - - '\bfix(?:es|ed)?\b' - feature_patterns: - - '\badd(?:s|ed)?\b' - - '\bimplement(?:s|ed)?\b' - filter: - excluded_paths: - - 'vendor/*' - - 'tests/*' - - 'app/Templates/*' - paths: { } diff --git a/sources/.travis.yml b/sources/.travis.yml deleted file mode 100644 index 66f9c28..0000000 --- a/sources/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: php - -php: - - "5.6" - - "5.5" - - "5.4" - - "5.3" - -before_script: wget https://phar.phpunit.de/phpunit.phar -script: php phpunit.phar -c tests/units.sqlite.xml \ No newline at end of file diff --git a/sources/README.markdown b/sources/README.markdown deleted file mode 100644 index d827473..0000000 --- a/sources/README.markdown +++ /dev/null @@ -1,165 +0,0 @@ -Kanboard -======== - -Kanboard is a simple visual task board web application. - -Official website: - -- Inspired by the [Kanban methodology](http://en.wikipedia.org/wiki/Kanban) -- Get a visual and clear overview of your project -- Multiple boards with the ability to drag and drop tasks -- Minimalist software, focus only on essential features (Less is more) -- Open source and self-hosted -- Super simple installation - -[![Build Status](https://travis-ci.org/fguillot/kanboard.svg)](https://travis-ci.org/fguillot/kanboard) - -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fguillot/kanboard/badges/quality-score.png?s=2b6490781608657cc8c43d02285bfafb4f489528)](https://scrutinizer-ci.com/g/fguillot/kanboard/) - -Features --------- - -- Multiple boards/projects -- Boards customization, rename/add/remove columns -- Tasks with different colors, categories, sub-tasks, attachments, comments and Markdown support for the description -- Automatic actions based on events -- Users management with a basic privileges separation (administrator or regular user) -- Email notifications -- External authentication: Google, GitHub, LDAP/ActiveDirectory and Reverse-Proxy -- Webhooks to create tasks from an external software -- A basic command line interface -- Host anywhere (shared hosting, VPS, Raspberry Pi or localhost) -- No external dependencies -- **Super easy setup**, copy and paste files and you are done! -- Translations in English, French, Brazilian Portuguese, Spanish, German, Polish, Swedish, Finnish, Italian, Chinese, Russian... - -Known bugs and feature requests -------------------------------- - -See Issues: - -License -------- - -GNU Affero General Public License version 3: - -Documentation -------------- - -### Using Kanboard - -#### Introduction - -- [Usage examples](docs/usage-examples.markdown) - -#### Working with projects - -- [Creating projects](docs/creating-projects.markdown) -- [Editing projects](docs/editing-projects.markdown) -- [Sharing boards and tasks](docs/sharing-projects.markdown) -- [Automatic actions](docs/automatic-actions.markdown) - -#### Working with tasks - -- [Creating tasks](docs/creating-tasks.markdown) - -#### Working with users - -- [User management](docs/manage-users.markdown) - -#### More - -- [Syntax guide](docs/syntax-guide.markdown) - -### Technical details - -#### Installation - -- [Installation instructions](docs/installation.markdown) -- [Upgrade Kanboard to a new version](docs/update.markdown) -- [Installation on Ubuntu](docs/ubuntu-installation.markdown) -- [Installation on Debian](docs/debian-installation.markdown) -- [Installation on Centos](docs/centos-installation.markdown) -- [Installation on Windows Server with IIS](docs/windows-iis-installation.markdown) -- [Example with Nginx + HTTPS + SPDY + PHP-FPM](docs/nginx-ssl-php-fpm.markdown) - -#### Database - -- [Sqlite database management](docs/sqlite-database.markdown) -- [How to use Mysql](docs/mysql-configuration.markdown) -- [How to use Postgresql](docs/postgresql-configuration.markdown) - -#### Authentication - -- [LDAP authentication](docs/ldap-authentication.markdown) -- [Google authentication](docs/google-authentication.markdown) -- [GitHub authentication](docs/github-authentication.markdown) -- [Reverse proxy authentication](docs/reverse-proxy-authentication.markdown) - -#### Developers and sysadmins - -- [Board configuration](docs/board-configuration.markdown) -- [Email configuration](docs/email-configuration.markdown) -- [Command line interface](docs/cli.markdown) -- [Json-RPC API](docs/api-json-rpc.markdown) -- [Webhooks](docs/webhooks.markdown) -- [How to use Kanboard with Vagrant](docs/vagrant.markdown) - -### Contributors - -- [Translations](docs/translations.markdown) -- [Coding standards](docs/coding-standards.markdown) -- [Running tests](docs/tests.markdown) - -The documentation is written in [Markdown](http://en.wikipedia.org/wiki/Markdown). -If you want to improve the documentation, just send a pull-request. - -FAQ ---- - -Go to the official website: - -Authors -------- - -Original author: [Frédéric Guillot](http://fredericguillot.com/) - -Contributors: - -- Alex Butum -- Ashish Kulkarni: https://github.com/ashkulz -- Claudio Lobo -- Cmer: https://github.com/chncsu -- Floaltvater: https://github.com/floaltvater -- Gavlepeter: https://github.com/gavlepeter -- Janne Mäntyharju: https://github.com/JanneMantyharju -- Jesusaplsoft: https://github.com/jesusaplsoft -- Kiswa: https://github.com/kiswa -- Kralo: https://github.com/kralo -- Levlaz: https://github.com/levlaz -- Lim Yuen Hoe: https://github.com/jasonmoofang -- Mathgl67: https://github.com/mathgl67 -- Matthieu Keller: https://github.com/maggick -- Mauro Mariño: https://github.com/moromarino -- Maxime: https://github.com/EpocDotFr -- Moraxy: https://github.com/moraxy -- Nala Ginrut: https://github.com/NalaGinrut -- Nekohayo: https://github.com/nekohayo -- Nramel: https://github.com/nramel -- Null-Kelvin: https://github.com/Null-Kelvin -- Olivier Maridat: https://github.com/oliviermaridat -- Poikilotherm: https://github.com/poikilotherm -- Rafaelrossa: https://github.com/rafaelrossa -- Raphaël Doursenaud: https://github.com/rdoursenaud -- Rzeka: https://github.com/rzeka -- Sebastien pacilly: https://github.com/spacilly -- Sylvain Veyrié: https://github.com/turb -- Toomyem: https://github.com/Toomyem -- Tony G. Bolaño: https://github.com/tonybolanyo -- Torsten: https://github.com/misterfu -- Troloo: https://github.com/troloo -- Typz: https://github.com/Typz -- Vedovator: https://github.com/vedovator -- Ybarc: https://github.com/ybarc - -There is also many people who have reported bugs or proposed awesome ideas. diff --git a/sources/Vagrantfile b/sources/Vagrantfile deleted file mode 100644 index f1b7a6f..0000000 --- a/sources/Vagrantfile +++ /dev/null @@ -1,29 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -VAGRANTFILE_API_VERSION = "2" - -$script = <'; } +/** + * Add a stylesheet asset + * + * @param string $filename Filename + * @return string + */ function css($filename) { return ''; } +/** + * Load a template + * + * @param string $name Template name + * @param array $args Template parameters + * @return string + */ function template($name, array $args = array()) { - $tpl = new \Core\Template; + $tpl = new Template; return $tpl->load($name, $args); } +/** + * Check if the given user_id is the connected user + * + * @param integer $user_id User id + * @return boolean + */ function is_current_user($user_id) { return $_SESSION['user']['id'] == $user_id; } +/** + * Check if the current user is administrator + * + * @return boolean + */ function is_admin() { return $_SESSION['user']['is_admin'] == 1; } -function get_username($user = false) +/** + * Return the username + * + * @param array $user User properties (optional) + * @return string + */ +function get_username(array $user = array()) { - return $user ? ($user['name'] ?: $user['username']) + return ! empty($user) ? ($user['name'] ?: $user['username']) : ($_SESSION['user']['name'] ?: $_SESSION['user']['username']); } +/** + * Get the current user id + * + * @return integer + */ function get_user_id() { return $_SESSION['user']['id']; } -function parse($text) +/** + * Markdown transformation + * + * @param string $text Markdown content + * @param array $link Link parameters for replacement + * @return string + */ +function markdown($text, array $link = array('controller' => 'task', 'action' => 'show', 'params' => array())) { - $text = markdown($text); - $text = preg_replace('!#(\d+)!i', '$0', $text); - return $text; -} - -function markdown($text) -{ - require_once __DIR__.'/../vendor/Michelf/MarkdownExtra.inc.php'; - - $parser = new \Michelf\MarkdownExtra; - $parser->no_markup = true; - $parser->no_entities = true; - - return $parser->transform($text); + $html = Parsedown::instance() + ->setMarkupEscaped(true) # escapes markup (HTML) + ->text($text); + + // Replace task #123 by a link to the task + $html = preg_replace_callback('!#(\d+)!i', function($matches) use ($link) { + return a( + $matches[0], + $link['controller'], + $link['action'], + $link['params'] + array('task_id' => $matches[1]) + ); + }, $html); + + return $html; } +/** + * Get the current URL without the querystring + * + * @return string + */ function get_current_base_url() { - $url = \Core\Tool::isHTTPS() ? 'https://' : 'http://'; + $url = Tool::isHTTPS() ? 'https://' : 'http://'; $url .= $_SERVER['SERVER_NAME']; $url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT']; $url .= dirname($_SERVER['PHP_SELF']) !== '/' ? dirname($_SERVER['PHP_SELF']).'/' : '/'; @@ -74,11 +141,23 @@ function get_current_base_url() return $url; } +/** + * HTML escaping + * + * @param string $value Value to escape + * @return string + */ function escape($value) { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false); } +/** + * Dispplay the flash session message + * + * @param string $html HTML wrapper + * @return string + */ function flash($html) { $data = ''; @@ -91,6 +170,12 @@ function flash($html) return $data; } +/** + * Display the flash session error message + * + * @param string $html HTML wrapper + * @return string + */ function flash_error($html) { $data = ''; @@ -103,6 +188,13 @@ function flash_error($html) return $data; } +/** + * Format a file size + * + * @param integer $size Size in bytes + * @param integer $precision Precision + * @return string + */ function format_bytes($size, $precision = 2) { $base = log($size) / log(1024); @@ -111,11 +203,14 @@ function format_bytes($size, $precision = 2) return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)]; } -function get_host_from_url($url) -{ - return escape(parse_url($url, PHP_URL_HOST)) ?: $url; -} - +/** + * Truncate a long text + * + * @param string $value Text + * @param integer $max_length Max Length + * @param string $end Text end + * @return string + */ function summary($value, $max_length = 85, $end = '[...]') { $length = strlen($value); @@ -127,11 +222,26 @@ function summary($value, $max_length = 85, $end = '[...]') return $value; } +/** + * Return true if needle is contained in the haystack + * + * @param string $haystack Haystack + * @param string $needle Needle + * @return boolean + */ function contains($haystack, $needle) { return strpos($haystack, $needle) !== false; } +/** + * Return a value from a dictionary + * + * @param mixed $id Key + * @param array $listing Dictionary + * @param string $default_value Value displayed when the key doesn't exists + * @return string + */ function in_list($id, array $listing, $default_value = '?') { if (isset($listing[$id])) { @@ -141,11 +251,25 @@ function in_list($id, array $listing, $default_value = '?') return $default_value; } +/** + * Display the form error class + * + * @param array $errors Error list + * @param string $name Field name + * @return string + */ function error_class(array $errors, $name) { return ! isset($errors[$name]) ? '' : ' form-error'; } +/** + * Display a list of form errors + * + * @param array $errors List of errors + * @param string $name Field name + * @return string + */ function error_list(array $errors, $name) { $html = ''; @@ -164,6 +288,13 @@ function error_list(array $errors, $name) return $html; } +/** + * Get an escaped form value + * + * @param mixed $values Values + * @param string $name Field name + * @return string + */ function form_value($values, $name) { if (isset($values->$name)) { @@ -173,23 +304,39 @@ function form_value($values, $name) return isset($values[$name]) ? 'value="'.escape($values[$name]).'"' : ''; } +/** + * Hidden CSRF token field + * + * @return string + */ function form_csrf() { return ''; } -function form_hidden($name, $values = array()) +/** + * Display a hidden form field + * + * @param string $name Field name + * @param array $values Form values + * @return string + */ +function form_hidden($name, array $values = array()) { return ''; } -function form_default_select($name, array $options, $values = array(), array $errors = array(), $class = '') -{ - $options = array('' => '?') + $options; - return form_select($name, $options, $values, $errors, $class); -} - -function form_select($name, array $options, $values = array(), array $errors = array(), $class = '') +/** + * Display a select field + * + * @param string $name Field name + * @param array $options Options + * @param array $values Form values + * @param array $errors Form errors + * @param string $class CSS class + * @return string + */ +function form_select($name, array $options, array $values = array(), array $errors = array(), $class = '') { $html = ''.escape($label).''; } +/** + * Display a checkbox field + * + * @param string $name Field name + * @param string $label Form label + * @param string $value Form value + * @param boolean $checked Field selected or not + * @param string $class CSS class + * @return string + */ function form_checkbox($name, $label, $value, $checked = false, $class = '') { return ''; } +/** + * Display a form label + * + * @param string $name Field name + * @param string $label Form label + * @param array $attributes HTML attributes + * @return string + */ function form_label($label, $name, array $attributes = array()) { return ''; } +/** + * Display a textarea + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { $class .= error_class($errors, $name); @@ -249,6 +442,17 @@ function form_textarea($name, $values = array(), array $errors = array(), array return $html; } +/** + * Display a input field + * + * @param string $type HMTL input tag type + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_input($type, $name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { $class .= error_class($errors, $name); @@ -261,32 +465,198 @@ function form_input($type, $name, $values = array(), array $errors = array(), ar return $html; } +/** + * Display a text field + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_text($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('text', $name, $values, $errors, $attributes, $class); } +/** + * Display a password field + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_password($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('password', $name, $values, $errors, $attributes, $class); } +/** + * Display an email field + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_email($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('email', $name, $values, $errors, $attributes, $class); } +/** + * Display a date field + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_date($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('date', $name, $values, $errors, $attributes, $class); } +/** + * Display a number field + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_number($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('number', $name, $values, $errors, $attributes, $class); } +/** + * Display a numeric field (allow decimal number) + * + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ function form_numeric($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') { return form_input('text', $name, $values, $errors, $attributes, $class.' form-numeric'); } + +/** + * Link + * + * a('link', 'task', 'show', array('task_id' => $task_id)) + * + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $class CSS class attribute + * @return string + */ +function a($label, $controller, $action, array $params = array(), $csrf = false, $class = '') +{ + return ''.$label.''; +} + +/** + * URL query string + * + * u('task', 'show', array('task_id' => $task_id)) + * + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @return string + */ +function u($controller, $action, array $params = array(), $csrf = false) +{ + $html = '?controller='.$controller.'&action='.$action; + + if ($csrf) { + $params['csrf_token'] = Security::getCSRFToken(); + } + + foreach ($params as $key => $value) { + $html .= '&'.$key.'='.$value; + } + + return $html; +} + +/** + * Pagination links + * + * @param array $pagination Pagination information + * @return string + */ +function paginate(array $pagination) +{ + extract($pagination); + + $html = ''; + + return $html; +} + +/** + * Column sorting (work with pagination) + * + * @param string $label Column title + * @param string $column SQL column name + * @param array $pagination Pagination information + * @return string + */ +function order($label, $column, array $pagination) +{ + extract($pagination); + + $prefix = ''; + + if ($order === $column) { + $prefix = $direction === 'DESC' ? '▼ ' : '▲ '; + $direction = $direction === 'DESC' ? 'ASC' : 'DESC'; + } + + $order = $column; + + return $prefix.a($label, $controller, $action, $params + compact('offset', 'order', 'direction')); +} diff --git a/sources/assets/css/app.css b/sources/assets/css/app.css index 3f6e131..8ed38ea 100644 --- a/sources/assets/css/app.css +++ b/sources/assets/css/app.css @@ -121,6 +121,16 @@ td li { background: rgb(219, 235, 255) } +th a { + text-decoration: none; + color: #333; +} + +th a:focus, +th a:hover { + text-decoration: underline; +} + /* forms */ form { padding: 10px; @@ -187,17 +197,17 @@ select { } ::-webkit-input-placeholder { - color: #bbb; + color: #ddd; padding-top: 2px; } ::-ms-input-placeholder { - color: #bbb; + color: #ddd; padding-top: 2px; } ::-moz-placeholder { - color: #bbb; + color: #ddd; padding-top: 2px; } @@ -222,6 +232,10 @@ textarea.form-error { list-style-type: none; } +ul.form-errors li { + margin-left: 0; +} + .form-help { font-size: 0.8em; color: brown; @@ -350,6 +364,15 @@ input.form-date { border-color: #ddd; } +.alert ul { + margin-top: 10px; + margin-bottom: 10px; +} + +.alert li { + margin-left: 25px; +} + /* labels */ a.label { text-decoration: none; @@ -508,6 +531,15 @@ nav .active a { border-bottom: 1px dotted red; } +.page-header h2 a { + color: #ddd; +} + +.page-header h2 a:focus, +.page-header h2 a:hover { + color: #333; +} + .page-header ul { text-align: left; margin-top: 5px; @@ -568,9 +600,14 @@ a.filter-on { margin-top: 5px; } +#board { + table-layout: fixed; +} + #board th a { text-decoration: none; font-size: 150%; + color: #3366CC; } #board td { @@ -615,6 +652,7 @@ a.filter-on { border: 1px solid #000; padding: 5px; font-size: 95%; + word-wrap: break-word; } .task-board-recent { @@ -720,18 +758,21 @@ a.task-board-nobody { } /* task view */ +.config-show, .user-show, .project-show, .task-show { position: relative; } +.config-show-main, .user-show-main, .project-show-main, .task-show-main { margin-left: 330px; } +.config-show-sidebar, .user-show-sidebar, .project-show-sidebar, .task-show-sidebar { @@ -746,6 +787,7 @@ a.task-board-nobody { border-radius: 5px; } +.config-show-sidebar li, .user-show-sidebar li, .project-show-sidebar li, .task-show-sidebar li { @@ -828,6 +870,12 @@ a.task-board-nobody { margin-top: 10px; } +.task-time-form { + margin-top: 10px; + margin-bottom: 25px; + padding: 3px; +} + /* comments */ .comment { margin-bottom: 20px; @@ -891,6 +939,11 @@ a.task-board-nobody { max-width: 800px; } +#comments .comment-textarea { + height: 80px; + width: 500px; +} + /* subtasks */ .subtasks-table { font-size: 0.85em; @@ -900,15 +953,6 @@ a.task-board-nobody { vertical-align: middle; } -.subtasks-time-tracking h4 { - margin-bottom: 5px; -} - -.subtasks-time-tracking li { - list-style-type: square; - margin-left: 30px; -} - /* markdown content */ .markdown { line-height: 1.4em; @@ -1000,9 +1044,8 @@ tr td.task-orange, border-color: rgb(255, 172, 98); } -/* config page */ -.listing, -.settings { +/* listing block */ +.listing { border-radius: 4px; padding: 8px 35px 8px 14px; margin-bottom: 20px; @@ -1011,8 +1054,7 @@ tr td.task-orange, background-color: #f0f0f0; } -.listing li, -.settings li { +.listing li { list-style-type: square; margin-left: 20px; margin-bottom: 3px; @@ -1025,22 +1067,23 @@ tr td.task-orange, /* project view */ .project-listing { - border-left: 3px solid #000; - margin-left: 35px; - padding-bottom: 10px; - max-width: 700px; + max-width: 500px; + margin-left: 30px; } .project-listing li { - font-size: 1.3em; - line-height: 1.7em; - list-style-type: none; - margin-left: 20px; - border-bottom: 1px dashed #ccc; + font-weight: bold; + padding: 10px; + margin-top: 8px; + display: block; + border: 1px solid #ddd; + border-radius: 5px; } .project-listing li:hover { - border-color: #333; + border-color: #ccc; + background: #fcfcfc; + box-shadow: 0 0 8px rgba(200, 200, 200, 0.6); } .project-listing a { @@ -1080,6 +1123,37 @@ tr td.task-orange, padding-top: 5px; } +.activity-description ul { + margin-top: 10px; +} + +.activity-description li { + margin-left: 40px; + list-style-type: circle; + color: #555; +} + +.activity-description .markdown { + margin-top: 10px; + color: #555; +} + +/* dashboard */ +#dashboard table { + font-size: 0.95em; +} + +.dashboard-left-column { + width: 55%; + float: left; +} + +.dashboard-right-column { + margin-left: 5%; + width: 40%; + float: left; +} + /* confirmation box */ .confirm { max-width: 700px; @@ -1111,6 +1185,19 @@ tr td.task-orange, font-size: 0.8em; } +/* pagination */ +#pagination { + text-align: center; +} + +#pagination-next { + margin-left: 5px; +} + +#pagination-previous { + margin-right: 5px; +} + /* responsive design */ @media only screen and (min-width : 768px) and (max-width : 1024px) { diff --git a/sources/assets/js/app.js b/sources/assets/js/app.js index 812cd03..7dcf04e 100644 --- a/sources/assets/js/app.js +++ b/sources/assets/js/app.js @@ -64,7 +64,17 @@ var Kanboard = (function() { $(".form-date").datepicker({ showOtherMonths: true, selectOtherMonths: true, - dateFormat: 'yy-mm-dd' + dateFormat: 'yy-mm-dd', + constrainInput: false + }); + + // Project select box + $("#board-selector").chosen({ + width: 180 + }); + + $("#board-selector").change(function() { + window.location = "?controller=board&action=show&project_id=" + $(this).val(); }); } }; @@ -228,17 +238,11 @@ Kanboard.Board = (function() { return { Init: function() { + + Kanboard.Before(); + board_load_events(); filter_load_events(); - - // Project select box - $("#board-selector").chosen({ - width: 180 - }); - - $("#board-selector").change(function() { - window.location = "?controller=board&action=show&project_id=" + $(this).val(); - }); } }; @@ -273,15 +277,29 @@ Kanboard.Project = (function() { })(); +// Dashboard related functions +Kanboard.Dashboard = (function() { + + return { + Init: function() { + Kanboard.Before(); + } + }; + +})(); + + // Initialization $(function() { -//alert($(window).width()); if ($("#board").length) { Kanboard.Board.Init(); } else if ($("#task-section").length) { Kanboard.Task.Init(); } + else if ($("#dashboard").length) { + Kanboard.Dashboard.Init(); + } else if ($("#project-section").length) { Kanboard.Project.Init(); } diff --git a/sources/config.default.php b/sources/config.default.php index 5a618b6..e2b5cdf 100644 --- a/sources/config.default.php +++ b/sources/config.default.php @@ -1,8 +1,5 @@ authentication('jsonrpc', '19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929'); - -print_r($client->getAllProjects()); - -``` - -The response: - -``` -Array -( - [0] => Array - ( - [id] => 1 - [name] => API test - [is_active] => 1 - [token] => 6bd0932fe7f4b5e6e4bc3c72800bfdef36a2c5de2f38f756dfb5bd632ebf - [last_modified] => 1403392631 - ) - -) -``` - -Procedures ----------- - -### createProject - -- Purpose: **Create a new project** -- Parameters: - - **name** (string, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createProject", - "id": 1797076613, - "params": { - "name": "PHP client" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1797076613, - "result": true -} -``` - -### getProjectById - -- Purpose: **Get project information** -- Parameters: - - **project_id** (integer, required) -- Result on success: **project properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getProjectById", - "id": 226760253, - "params": { - "project_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 226760253, - "result": { - "id": "1", - "name": "API test", - "is_active": "1", - "token": "", - "last_modified": "1410263246", - "is_public": "0" - } -} -``` - -### getProjectByName - -- Purpose: **Get project information** -- Parameters: - - **name** (string, required) -- Result on success: **project properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getProjectByName", - "id": 1620253806, - "params": { - "name": "Test" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1620253806, - "result": { - "id": "1", - "name": "Test", - "is_active": "1", - "token": "", - "last_modified": "0", - "is_public": "0" - } -} -``` - -### getAllProjects - -- Purpose: **Get all available projects** -- Parameters: - - **none** -- Result on success: **List of projects** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllProjects", - "id": 134982303 -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 134982303, - "result": [ - { - "id": "2", - "name": "PHP client", - "is_active": "1", - "token": "", - "last_modified": "0", - "is_public": "0" - }, - { - "id": "1", - "name": "Test", - "is_active": "1", - "token": "", - "last_modified": "0", - "is_public": "0" - } - ] -} -``` - -### updateProject - -- Purpose: **Update a project** -- Parameters: - - **id** (integer, required) - - **name** (string, required) - - **is_active** (integer, optional) - - **token** (string, optional) - - **is_public** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateProject", - "id": 1853996288, - "params": { - "id": 1, - "name": "PHP client update" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1853996288, - "result": true -} -``` - -### removeProject - -- Purpose: **Remove a project** -- Parameters: - **project_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeProject", - "id": 46285125, - "params": { - "project_id": "2" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 46285125, - "result": true -} -``` - -### enableProject - -- Purpose: **Enable a project** -- Parameters: - - **project_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "enableProject", - "id": 1775494839, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1775494839, - "result": true -} -``` - -### disableProject - -- Purpose: **Disable a project** -- Parameters: - - **project_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "disableProject", - "id": 1734202312, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1734202312, - "result": true -} -``` - -### enableProjectPublicAccess - -- Purpose: **Enable public access for a given project** -- Parameters: - - **project_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "enableProjectPublicAccess", - "id": 103792571, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 103792571, - "result": true -} -``` - -### disableProjectPublicAccess - -- Purpose: **Disable public access for a given project** -- Parameters: - - **project_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "disableProjectPublicAccess", - "id": 942472945, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 942472945, - "result": true -} -``` - -### getAllowedUsers - -- Purpose: **Get allowed users for a given project** -- Note: Only people explicitly allowed are part of this list, administrators are always authorized -- Parameters: - - **project_id** (integer, required) -- Result on success: Key/value pair of user_id and username -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllowedUsers", - "id": 1944388643, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1944388643, - "result": { - "1": "user1", - "2": "user2", - "3": "user3" - } -} -``` - -### revokeUser - -- Purpose: **Revoke user access for a given project** -- Parameters: - - **project_id** (integer, required) - - **user_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "revokeUser", - "id": 251218350, - "params": [ - 1, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 251218350, - "result": true -} -``` - -### allowUser - -- Purpose: **Grant user access for a given project** -- Parameters: - - **project_id** (integer, required) - - **user_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "allowUser", - "id": 2111451404, - "params": [ - 1, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 2111451404, - "result": true -} -``` - - -### getBoard - -- Purpose: **Get all necessary information to display a board** -- Parameters: - - **project_id** (integer, required) -- Result on success: **board properties** -- Result on failure: **empty list** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getBoard", - "id": 1627282648, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1627282648, - "result": [ - { - "id": "1", - "title": "Backlog", - "position": "1", - "project_id": "1", - "task_limit": "0", - "tasks": [] - }, - { - "id": "2", - "title": "Ready", - "position": "2", - "project_id": "1", - "task_limit": "0", - "tasks": [] - }, - { - "id": "3", - "title": "Work in progress", - "position": "3", - "project_id": "1", - "task_limit": "0", - "tasks": [ - { - "nb_comments": "0", - "nb_files": "0", - "nb_subtasks": "1", - "nb_completed_subtasks": "0", - "id": "1", - "title": "Task with comment", - "description": "", - "date_creation": "1410952872", - "date_modification": "1410952872", - "date_completed": null, - "date_due": "0", - "color_id": "red", - "project_id": "1", - "column_id": "3", - "owner_id": "1", - "creator_id": "0", - "position": "1", - "is_active": "1", - "score": "0", - "category_id": "0", - "assignee_username": "admin", - "assignee_name": null - } - ] - }, - ... - ] -} -``` - -### getColumns - -- Purpose: **Get all columns information for a given project** -- Parameters: - - **project_id** (integer, required) -- Result on success: **columns properties** -- Result on failure: **empty list** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getColumns", - "id": 887036325, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 887036325, - "result": [ - { - "id": "1", - "title": "Backlog", - "position": "1", - "project_id": "1", - "task_limit": "0" - }, - { - "id": "2", - "title": "Ready", - "position": "2", - "project_id": "1", - "task_limit": "0" - }, - { - "id": "3", - "title": "Work in progress", - "position": "3", - "project_id": "1", - "task_limit": "0" - } - ] -} -``` - -### getColumn - -- Purpose: **Get a single column** -- Parameters: - - **column_id** (integer, required) -- Result on success: **column properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getColumn", - "id": 1242049935, - "params": [ - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1242049935, - "result": { - "id": "2", - "title": "Youpi", - "position": "2", - "project_id": "1", - "task_limit": "5" - } -} -``` - -### moveColumnUp - -- Purpose: **Move up the column position** -- Parameters: - - **project_id** (integer, required) - - **column_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "moveColumnUp", - "id": 99275573, - "params": [ - 1, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 99275573, - "result": true -} -``` - -### moveColumnDown - -- Purpose: **Move down the column position** -- Parameters: - - **project_id** (integer, required) - - **column_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "moveColumnDown", - "id": 957090649, - "params": { - "project_id": 1, - "column_id": 2 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 957090649, - "result": true -} -``` - -### updateColumn - -- Purpose: **Update column properties** -- Parameters: - - **column_id** (integer, required) - - **title** (string, required) - - **task_limit** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateColumn", - "id": 480740641, - "params": [ - 2, - "Boo", - 5 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 480740641, - "result": true -} -``` - -### addColumn - -- Purpose: **Add a new column** -- Parameters: - - **project_id** (integer, required) - - **title** (string, required) - - **task_limit** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "addColumn", - "id": 638544704, - "params": [ - 1, - "Boo" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 638544704, - "result": true -} -``` - -### removeColumn - -- Purpose: **Remove a column** -- Parameters: - - **column_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeColumn", - "id": 1433237746, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1433237746, - "result": true -} -``` - - -### createTask - -- Purpose: **Create a new task** -- Parameters: - - **title** (string, required) - - **project_id** (integer, required) - - **color_id** (string, optional) - - **column_id** (integer, optional) - - **description** Markdown content (string, optional) - - **owner_id** (integer, optional) - - **creator_id** (integer, optional) - - **score** (integer, optional) - - **date_due**: ISO8601 format (string, optional) - - **category_id** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createTask", - "id": 1176509098, - "params": { - "owner_id": 1, - "creator_id": 0, - "date_due": "", - "description": "", - "category_id": 0, - "score": 0, - "title": "Test", - "project_id": 1, - "color_id": "green", - "column_id": 2 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1176509098, - "result": true -} -``` - -### getTask - -- Purpose: **Get task information** -- Parameters: - - **task_id** (integer, required) -- Result on success: **task properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getTask", - "id": 700738119, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 700738119, - "result": { - "id": "1", - "title": "Task #1", - "description": "", - "date_creation": "1409963206", - "color_id": "blue", - "project_id": "1", - "column_id": "2", - "owner_id": "1", - "position": "1", - "is_active": "1", - "date_completed": null, - "score": "0", - "date_due": "0", - "category_id": "0", - "creator_id": "0", - "date_modification": "1409963206" - } -} -``` - -### getAllTasks - -- Purpose: **Get all available tasks** -- Parameters: - - **project_id** (integer, required) - - **status**: The value 1 for active tasks and 0 for inactive (integer, required) -- Result on success: **List of tasks** -- Result on failure: **false** - -Request example to fetch all tasks on the board: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllTasks", - "id": 133280317, - "params": { - "project_id": 1, - "status": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 133280317, - "result": [ - { - "id": "1", - "title": "Task #1", - "description": "", - "date_creation": "1409961789", - "color_id": "blue", - "project_id": "1", - "column_id": "2", - "owner_id": "1", - "position": "1", - "is_active": "1", - "date_completed": null, - "score": "0", - "date_due": "0", - "category_id": "0", - "creator_id": "0", - "date_modification": "1409961789" - }, - { - "id": "2", - "title": "Test", - "description": "", - "date_creation": "1409962115", - "color_id": "green", - "project_id": "1", - "column_id": "2", - "owner_id": "1", - "position": "2", - "is_active": "1", - "date_completed": null, - "score": "0", - "date_due": "0", - "category_id": "0", - "creator_id": "0", - "date_modification": "1409962115" - }, - ... - ] -} -``` - -### updateTask - -- Purpose: **Update a task** -- Parameters: - - **id** (integer, required) - - **title** (string, optional) - - **color_id** (string, optional) - - **project_id** (integer, optional) - - **column_id** (integer, optional) - - **description** Markdown content (string, optional) - - **owner_id** (integer, optional) - - **creator_id** (integer, optional) - - **score** (integer, optional) - - **date_due**: ISO8601 format (string, optional) - - **category_id** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example to change the task color: - -```json -{ - "jsonrpc": "2.0", - "method": "updateTask", - "id": 1406803059, - "params": { - "id": 1, - "color_id": "blue" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1406803059, - "result": true -} -``` - -### openTask - -- Purpose: **Set a task to the status open** -- Parameters: - - **task_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "openTask", - "id": 1888531925, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1888531925, - "result": true -} -``` - -### closeTask - -- Purpose: **Set a task to the status close** -- Parameters: - - **task_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "closeTask", - "id": 1654396960, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1654396960, - "result": true -} -``` - -### removeTask - -- Purpose: **Remove a task** -- Parameters: - - **task_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeTask", - "id": 1423501287, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1423501287, - "result": true -} -``` - -### moveTaskPosition - -- Purpose: **Move a task to another column or another position** -- Parameters: - - **project_id** (integer, required) - - **task_id** (integer, required) - - **column_id** (integer, required) - - **position** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "moveTaskPosition", - "id": 117211800, - "params": { - "project_id": 1, - "task_id": 1, - "column_id": 2, - "position": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 117211800, - "result": true -} -``` - -### createUser - -- Purpose: **Create a new user** -- Parameters: - - **username** Must be unique (string, required) - - **password** Must have at least 6 characters (string, required) - - **name** (string, optional) - - **email** (string, optional) - - **is_admin** Set the value 1 for admins or 0 for regular users (integer, optional) - - **default_project_id** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createUser", - "id": 1518863034, - "params": { - "username": "biloute", - "password": "123456" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1518863034, - "result": true -} -``` - -### getUser - -- Purpose: **Get user information** -- Parameters: - - **user_id** (integer, required) -- Result on success: **user properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getUser", - "id": 1769674781, - "params": { - "user_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1769674781, - "result": { - "id": "1", - "username": "biloute", - "password": "$2y$10$dRs6pPoBu935RpmsrhmbjevJH5MgZ7Kr9QrnVINwwyZ3.MOwqg.0m", - "is_admin": "0", - "default_project_id": "0", - "is_ldap_user": "0", - "name": "", - "email": "", - "google_id": null, - "github_id": null, - "notifications_enabled": "0" - } -} -``` - -### getAllUsers - -- Purpose: **Get all available users** -- Parameters: - - **none** -- Result on success: **List of users** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllUsers", - "id": 1438712131 -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1438712131, - "result": [ - { - "id": "1", - "username": "biloute", - "name": "", - "email": "", - "is_admin": "0", - "default_project_id": "0", - "is_ldap_user": "0", - "notifications_enabled": "0", - "google_id": null, - "github_id": null - }, - ... - ] -} -``` - -### updateUser - -- Purpose: **Update a user** -- Parameters: - - **id** (integer) - - **username** (string, optional) - - **name** (string, optional) - - **email** (string, optional) - - **is_admin** (integer, optional) - - **default_project_id** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateUser", - "id": 322123657, - "params": { - "id": 1, - "is_admin": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 322123657, - "result": true -} -``` - -### removeUser - -- Purpose: **Remove a user** -- Parameters: - - **user_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeUser", - "id": 2094191872, - "params": { - "user_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 2094191872, - "result": true -} -``` - - -### createCategory - -- Purpose: **Create a new category** -- Parameters: -- **project_id** (integer, required) - - **name** (string, required, must be unique for the given project) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createCategory", - "id": 541909890, - "params": { - "name": "Super category", - "project_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 541909890, - "result": true -} -``` - -### getCategory - -- Purpose: **Get category information** -- Parameters: - - **category_id** (integer, required) -- Result on success: **category properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getCategory", - "id": 203539163, - "params": { - "category_id": 1 - } -} -``` - -Response example: - -```json -{ - - "jsonrpc": "2.0", - "id": 203539163, - "result": { - "id": "1", - "name": "Super category", - "project_id": "1" - } -} -``` - -### getAllCategories - -- Purpose: **Get all available categories** -- Parameters: - - **project_id** (integer, required) -- Result on success: **List of categories** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllCategories", - "id": 1261777968, - "params": { - "project_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1261777968, - "result": [ - { - "id": "1", - "name": "Super category", - "project_id": "1" - } - ] -} -``` - -### updateCategory - -- Purpose: **Update a category** -- Parameters: - - **id** (integer, required) - - **name** (string, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateCategory", - "id": 570195391, - "params": { - "id": 1, - "name": "Renamed category" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 570195391, - "result": true -} -``` - -### removeCategory - -- Purpose: **Remove a category** -- Parameters: - - **category_id** (integer) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeCategory", - "id": 88225706, - "params": { - "category_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 88225706, - "result": true -} -``` - - -### createComment - -- Purpose: **Create a new comment** -- Parameters: - - **task_id** (integer, required) - - **user_id** (integer, required) - - **content** Markdown content (string, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createComment", - "id": 1580417921, - "params": { - "task_id": 1, - "user_id": 1, - "content": "Comment #1" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1580417921, - "result": true -} -``` - -### getComment - -- Purpose: **Get comment information** -- Parameters: - - **comment_id** (integer, required) -- Result on success: **comment properties** -- Result on failure: **null** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getComment", - "id": 867839500, - "params": { - "comment_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 867839500, - "result": { - "id": "1", - "task_id": "1", - "user_id": "1", - "date": "1410881970", - "comment": "Comment #1", - "username": "admin", - "name": null - } -} -``` - -### getAllComments - -- Purpose: **Get all available comments** -- Parameters: - - **task_id** (integer, required) -- Result on success: **List of comments** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllComments", - "id": 148484683, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 148484683, - "result": [ - { - "id": "1", - "date": "1410882272", - "task_id": "1", - "user_id": "1", - "comment": "Comment #1", - "username": "admin", - "name": null - }, - ... - ] -} -``` - -### updateComment - -- Purpose: **Update a comment** -- Parameters: - - **id** (integer, required) - - **content** Markdown content (string, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateComment", - "id": 496470023, - "params": { - "id": 1, - "content": "Comment #1 updated" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1493368950, - "result": true -} -``` - -### removeComment - -- Purpose: **Remove a comment** -- Parameters: - - **comment_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeComment", - "id": 328836871, - "params": { - "comment_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 328836871, - "result": true -} -``` - - -### createSubtask - -- Purpose: **Create a new subtask** -- Parameters: - - **task_id** (integer, required) - - **title** (integer, required) - - **assignee_id** (int, optional) - - **time_estimated** (int, optional) - - **time_spent** (int, optional) - - **status** (int, optional) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createSubtask", - "id": 2041554661, - "params": { - "task_id": 1, - "title": "Subtask #1" - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 2041554661, - "result": true -} -``` - -### getSubtask - -- Purpose: **Get subtask information** -- Parameters: - - **subtask_id** (integer) -- Result on success: **subtask properties** -- Result on failure: **null** - -```json -{ - "jsonrpc": "2.0", - "method": "getSubtask", - "id": 133184525, - "params": { - "subtask_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 133184525, - "result": { - "id": "1", - "title": "Subtask #1", - "status": "0", - "time_estimated": "0", - "time_spent": "0", - "task_id": "1", - "user_id": "0" - } -} -``` - -### getAllSubtasks - -- Purpose: **Get all available subtasks** -- Parameters: - - **task_id** (integer, required) -- Result on success: **List of subtasks** -- Result on failure: **false** - -```json -{ - "jsonrpc": "2.0", - "method": "getAllSubtasks", - "id": 2087700490, - "params": { - "task_id": 1 - } -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 2087700490, - "result": [ - { - "id": "1", - "title": "Subtask #1", - "status": "0", - "time_estimated": "0", - "time_spent": "0", - "task_id": "1", - "user_id": "0", - "username": null, - "name": null, - "status_name": "Todo" - }, - ... - ] -} -``` - -### updateSubtask - -- Purpose: **Update a subtask** -- Parameters: - - **id** (integer, required) - - **task_id** (integer, required) - - **title** (integer, optional) - - **assignee_id** (integer, optional) - - **time_estimated** (integer, optional) - - **time_spent** (integer, optional) - - **status** (integer, optional) -- Result on success: **true** -- Result on failure: **false** - -Request examples: - -```json -{ - "jsonrpc": "2.0", - "method": "updateSubtask", - "id": 191749979, - "params": { - "id": 1, - "task_id": 1, - "status": 1, - "time_spent": 5, - "user_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 191749979, - "result": true -} -``` - -### removeSubtask - -- Purpose: **Remove a subtask** -- Parameters: - - **subtask_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -```json -{ - "jsonrpc": "2.0", - "method": "removeSubtask", - "id": 1382487306, - "params": { - "subtask_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1382487306, - "result": true -} -``` diff --git a/sources/docs/automatic-actions.markdown b/sources/docs/automatic-actions.markdown deleted file mode 100644 index 631919e..0000000 --- a/sources/docs/automatic-actions.markdown +++ /dev/null @@ -1,97 +0,0 @@ -Automatic Actions -================= - -To minimize the user interaction, Kanboard support automatic actions. - -Each automatic action is defined like that: - -- An event to listen -- An action linked to this event -- Eventually there is some parameters to define - -Each project can have a different set of automatic actions, the configuration panel is located on the project listing page, just click on the link "Automatic actions". - -![Automatic action creation (step 1)](http://kanboard.net/screenshots/documentation/project-automatic-action-step1.png) - -To add a new automatic action, choose the event with an action and click on the button "Next Step", then specify action parameters and finish the process by clicking on the button "Save this action". - -![Automatic action creation (step 2)](http://kanboard.net/screenshots/documentation/project-automatic-action-step2.png) - -Each time an event occurs, the corresponding actions are executed. - -![Automatic actions](http://kanboard.net/screenshots/documentation/project-automatic-actions.png) - -List of available events ------------------------- - -- Move a task to another column -- Move a task to another position in the same column -- Task modification -- Task creation -- Open a closed task -- Closing a task -- Task creation or modification -- Task assignee change - -List of available actions -------------------------- - -- Close the task -- Assign the task to a specific user -- Assign the task to the person who does the action -- Duplicate the task to another project -- Move the task to another project -- Assign a color to a specific user -- Assign automatically a color based on a category -- Assign automatically a category based on a color - -Examples --------- - -Here are some examples used in the real life: - -### When I move a task to the column "Done", automatically close this task - -- Choose the event: **Move a task to another column** -- Choose the action: **Close the task** -- Define the action parameter: **Column = Done** (this is the destination column) - -### When I move a task to the column "To be validated", assign this task to a specific user - -- Choose the event: **Move a task to another column** -- Choose the action: **Assign the task to a specific user** -- Define the action parameters: **Column = To be validated** and **User = Bob** (Bob is our tester) - -### When I move a task to the column "Work in progress", assign this task to the current user - -- Choose the event: **Move a task to another column** -- Choose the action: **Assign the task to the person who does the action** -- Define the action parameter: **Column = Work in progress** - -### When a task is completed, duplicate this task to another project - -Let's say we have two projects "Customer orders" and "Production", once the order is validated, swap it to the "Production" project. - -- Choose the event: **Closing a task** -- Choose the action: **Duplicate the task to another project** -- Define the action parameters: **Column = Validated** and **Project = Production** - -### When a task is moved to the last column, move the exact same task to another project - -Let's say we have two projects "Ideas" and "Development", once the idea is validated, swap it to the "Development" project. - -- Choose the event: **Move a task to another column** -- Choose the action: **Move the task to another project** -- Define the action parameters: **Column = Validated** and **Project = Development** - -### I want to assign automatically a color to the user Bob - -- Choose the event: **Task assignee change** -- Choose the action: **Assign a color to a specific user** -- Define the action parameters: **Color = Green** and **Assignee = Bob** - -### I want to assign automatically a color to the defined category "Feature Request" - -- Choose the event: **Task creation or modification** -- Choose the action: **Assign automatically a color based on a category** -- Define the action parameters: **Color = Blue** and **Category = Feature Request** diff --git a/sources/docs/board-configuration.markdown b/sources/docs/board-configuration.markdown deleted file mode 100644 index cc59798..0000000 --- a/sources/docs/board-configuration.markdown +++ /dev/null @@ -1,30 +0,0 @@ -Board configuration -=================== - -Some parameters for the boards can be changed with a config file. - -Default values are available in the file `config.default.php`. -If you want to override the default values, you have to create a config file `config.php` in the root directory of your Kanboard installation. - -### Auto-refresh frequency for the public board view - -```php -// Auto-refresh frequency in seconds for the public board view (60 seconds by default) -define('BOARD_PUBLIC_CHECK_INTERVAL', 60); -``` - -### Auto-refresh frequency for the board (Ajax polling) - -```php -// Board refresh frequency in seconds (the value 0 disable this feature, 10 seconds by default) -define('BOARD_CHECK_INTERVAL', 10); -``` - -### Task highlighting - -Display a shadow around the task when a task was moved recently. Set the value 0 to disable this feature. - -```php -// Period (in seconds) to consider a task was modified recently (0 to disable, 2 days by default) -define('RECENT_TASK_PERIOD', 48*60*60); -``` diff --git a/sources/docs/centos-installation.markdown b/sources/docs/centos-installation.markdown deleted file mode 100644 index ccffa05..0000000 --- a/sources/docs/centos-installation.markdown +++ /dev/null @@ -1,69 +0,0 @@ -Centos Installation -=================== - -Centos 7 --------- - -Install PHP and Apache: - -```bash -yum install -y php php-mbstring php-pdo unzip wget -``` - -By default Centos 7 use PHP 5.4.16 and Apache 2.4.6. - -Restart Apache: - -```bash -systemctl restart httpd.service -``` - -Install Kanboard: - -```bash -cd /var/www/html -wget http://kanboard.net/kanboard-latest.zip -unzip kanboard-latest.zip -chown -R apache:apache kanboard/data -rm kanboard-latest.zip -``` - -If SeLinux is enabled, be sure that the Apache user can write to the directory data: - -```bash -chcon -R -t httpd_sys_content_rw_t /var/www/html/kanboard/data -``` - -Centos 6.5 ----------- - -Install PHP and Apache: - -```bash -yum install -y php php-mbstring php-pdo unzip wget -``` - -By default Centos 6.5 use PHP 5.3.3 and Apache 2.2.15. - -Enable short tags: - -- Edit the file `/etc/php.ini` -- Change the line `short_open_tag = On` - -Restart Apache: - -```bash -service httpd restart -``` - -Install Kanboard: - -```bash -cd /var/www/html -wget http://kanboard.net/kanboard-latest.zip -unzip kanboard-latest.zip -chown -R apache:apache kanboard/data -rm kanboard-latest.zip -``` - -Go to `http://your_server/kanboard/`. \ No newline at end of file diff --git a/sources/docs/cli.markdown b/sources/docs/cli.markdown deleted file mode 100644 index 4c4913e..0000000 --- a/sources/docs/cli.markdown +++ /dev/null @@ -1,54 +0,0 @@ -Command Line Interface -====================== - -Kanboard provide a simple command line interface that can be used from any Unix terminal. -This tool can be used only on the local machine. - -This feature is useful to run commands outside the web server by example a huge report. - -Usage ------ - -- Open a terminal and go to your Kanboard directory (example: `cd /var/www/kanboard`) -- Run the command `./kanboard` - -```bash -$ ./kanboard -Kanboard command line interface -=============================== - -- Task export to stdout (CSV format): ./kanboard export-csv -- Send notifications for due tasks: ./kanboard send-notifications-due-tasks -``` - -Available commands ------------------- - -### CSV export of tasks - -Usage: - -```bash -./kanboard export-csv -``` - -Example: - -```bash -./kanboard export-csv 1 2014-07-14 2014-07-20 > /tmp/my_custom_export.csv -``` - -### Send notifications for due tasks - -Emails will be sent to all users with notifications enabled. - -```bash -./kanboard send-notifications-due-tasks -``` - -Cronjob example: - -```bash -# Everyday at 8am we check for due tasks -0 8 * * * cd /path/to/kanboard && ./kanboard send-notifications-due-tasks >/dev/null 2>&1 -``` diff --git a/sources/docs/coding-standards.markdown b/sources/docs/coding-standards.markdown deleted file mode 100644 index a697519..0000000 --- a/sources/docs/coding-standards.markdown +++ /dev/null @@ -1,23 +0,0 @@ -Coding standards -================ - -PHP code --------- - -- Indentation: 4 spaces -- Line return: Unix => `\n` -- Encoding: UTF-8 -- Always write PHPdoc comments for methods and class properties -- Coding style: Try to follow [PSR-1](http://www.php-fig.org/psr/psr-1/) and [PSR-2](http://www.php-fig.org/psr/psr-2/) otherwise follow the actual style. - -Javascript code ---------------- - -- Indentation: 4 spaces -- Line return: Unix => `\n` - -CSS code --------- - -- Indentation: 4 spaces -- Line return: Unix => `\n` diff --git a/sources/docs/creating-projects.markdown b/sources/docs/creating-projects.markdown deleted file mode 100644 index fe19332..0000000 --- a/sources/docs/creating-projects.markdown +++ /dev/null @@ -1,14 +0,0 @@ -Creating projects -================= - -Kanboard can handle multiple projects. - -To create a new project, click on the top menu "projects" and click on the link "New project". - -![Project creation link](http://kanboard.net/screenshots/documentation/project-creation-link.png) - -Then the form appears: - -![Project creation form](http://kanboard.net/screenshots/documentation/project-creation-form.png) - -It's very easy, you just have to find a name for your project. diff --git a/sources/docs/creating-tasks.markdown b/sources/docs/creating-tasks.markdown deleted file mode 100644 index d8eb9e7..0000000 --- a/sources/docs/creating-tasks.markdown +++ /dev/null @@ -1,24 +0,0 @@ -Creating tasks -============== - -From the board, click on the plus sign next to the column name: - -![Task creation from the board](http://kanboard.net/screenshots/documentation/task-creation-board.png) - -Then the task creation form appears: - -![Task creation form](http://kanboard.net/screenshots/documentation/task-creation-form.png) - -The only mandatory field is the title. - -Field description: - -- **Title**: The title of your task, that will be displayed on the board. -- **Description**: Allow you to add more information about the task, the content can be written in [Markdown](http://kanboard.net/documentation/syntax-guide). -- **Create another task**: Check this box if you want to create a similar task (fields will be prefilled). -- **Assignee**: The person that will work on the task. -- **Category**: Only one category can be assign to a task. -- **Column**: The column where the task will be created, your task will be positioned at the bottom. -- **Color**: Choose the color of the card. -- **Complexity**: Used in agile project management (Scrum), the complexity or story points is a number that tells the team how hard the story is. Often, people use the fibonacci series. -- **Due Date**: Overdue tasks will have a red due date and upcoming due dates will be black on the board. Several date format are accepted in addition to the date picker. diff --git a/sources/docs/debian-installation.markdown b/sources/docs/debian-installation.markdown deleted file mode 100644 index 10cab44..0000000 --- a/sources/docs/debian-installation.markdown +++ /dev/null @@ -1,42 +0,0 @@ -How to install Kanboard on Debian? -================================== - -Debian 7 (Wheezy) ------------------ - -Install Apache and PHP: - -```bash -apt-get update -apt-get install -y php5 php5-sqlite unzip -``` - -Install Kanboard: - -```bash -cd /var/www -wget http://kanboard.net/kanboard-latest.zip -unzip kanboard-latest.zip -chown -R www-data:www-data kanboard/data -rm kanboard-latest.zip -``` - -Debian 6 (Squeeze) ------------------- - -Install Apache and PHP: - -```bash -apt-get update -apt-get install -y libapache2-mod-php5 php5-sqlite unzip -``` - -Install Kanboard: - -```bash -cd /var/www -wget http://kanboard.net/kanboard-latest.zip -unzip kanboard-latest.zip -chown -R www-data:www-data kanboard/data -rm kanboard-latest.zip -``` \ No newline at end of file diff --git a/sources/docs/editing-projects.markdown b/sources/docs/editing-projects.markdown deleted file mode 100644 index 1982785..0000000 --- a/sources/docs/editing-projects.markdown +++ /dev/null @@ -1,8 +0,0 @@ -Editing projects -================ - -Projects can be renamed and disabled at any time. - -To rename a project, just click on the link "Edit project" on the left. - -![Project edition](http://kanboard.net/screenshots/documentation/project-edition.png) diff --git a/sources/docs/email-configuration.markdown b/sources/docs/email-configuration.markdown deleted file mode 100644 index 63ed978..0000000 --- a/sources/docs/email-configuration.markdown +++ /dev/null @@ -1,112 +0,0 @@ -Email configuration -=================== - -User settings -------------- - -To receive email notifications, users of Kanboard must have: - -- Activated notifications in the settings page -- Have a valid email address in their profile - -Server settings ---------------- - -By default, Kanboard will use the bundled PHP mail function to send emails. -Usually that require no configuration if your server can already send emails. - -However, it's possible to use other methods, the SMTP protocol and Sendmail. - -### SMTP configuration - -Create a blank `config.php` file or use the template `config.default.php` and set those values: - -```php -// We choose "smtp" as mail transport -define('MAIL_TRANSPORT', 'smtp'); - -// We define our server settings -define('MAIL_SMTP_HOSTNAME', 'mail.example.com'); -define('MAIL_SMTP_PORT', 25); - -// Credentials for authentication on the SMTP server (not mandatory) -define('MAIL_SMTP_USERNAME', 'username'); -define('MAIL_SMTP_PASSWORD', 'super password'); -``` - -It's also possible to use a secure connection, TLS or SSL: - -```php -define('MAIL_SMTP_ENCRYPTION', 'ssl'); // Valid values are "null", "ssl" or "tls" -``` - -Here an example with Google: - -```php -define('MAIL_SMTP_HOSTNAME', 'smtp.gmail.com'); -define('MAIL_SMTP_PORT', 465); -define('MAIL_SMTP_USERNAME', 'my_account@gmail.com'); -define('MAIL_SMTP_PASSWORD', 'my google password'); -define('MAIL_SMTP_ENCRYPTION', 'ssl'); -``` - -To use Google, you might need to allow Kanboard to use your Google account, see ["Allowing less secure apps to access your account"](https://support.google.com/accounts/answer/6010255) and ["My client isn't accepting my username and password"](https://support.google.com/mail/answer/14257). - -### Sendmail configuration - -By default the sendmail command will be `/usr/sbin/sendmail -bs` but you can customize that in your config file. - -Example: - -```php -// We choose "sendmail" as mail transport -define('MAIL_TRANSPORT', 'sendmail'); - -// If you need to change the sendmail command, replace the value -define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/sendmail -bs'); -``` - -### The sender email address - -By default, emails will use the sender address `notifications@kanboard.net`. -It's not possible to reply to this address. - -You can customize this address by changing the value of the constant `MAIL_FROM` in your config file. - -```php -define('MAIL_FROM', 'notifications@kanboard.net'); -``` - -That can be useful if your SMTP server configuration doesn't accept the default address. - - -### Check for due tasks - -Every day, Kanboard can check for due tasks, to do that you have to setup a cronjob on your server and use the Kanboard command line interface. - -Here a example: - -```bash -# Everyday at 8am we check for due tasks -0 8 * * * cd /path/to/kanboard && ./kanboard send-notifications-due-tasks >/dev/null 2>&1 -``` - -### How to display a link to the task in notifications? - -To do that, you have to specify the URL of your Kanboard installation in your config file. -By default, nothing is defined, so no links will be displayed. - -```php -// Your Kanboard base URL, example: http://demo.kanboard.net/ (used by email notifications or CLI scripts) -define('KANBOARD_URL', ''); -``` - -Examples: - -- http://demo.kanboard.net/ -- http://myserver/kanboard/ -- http://kanboard.mydomain.com/ - -Don't forget the ending `/`. - -You need to define that manually because Kanboard can't guess the URL from a command line script and some people have very specific configuration. diff --git a/sources/docs/github-authentication.markdown b/sources/docs/github-authentication.markdown deleted file mode 100644 index 830f143..0000000 --- a/sources/docs/github-authentication.markdown +++ /dev/null @@ -1,62 +0,0 @@ -GitHub Authentication -===================== - -Requirements ------------- - -- OAuth GitHub API credentials (available in your [Settings > Applications > Developer applications](https://github.com/settings/applications)) - -How does this work? -------------------- - -The GitHub authentication in Kanboard uses the [OAuth 2.0](http://oauth.net/2/) protocol, so any user of Kanboard can be linked to a GitHub account. When that is done, they no longer need to manually login with their Kanboard account, but can simply automatically login with their GitHub account. - -How to link a GitHub account ----------------------------------- - -1. Login to Kanboard with the desired user -2. Go to the **Edit user** page and click on the link **Link my GitHub Account** -3. You are redirected to the GitHub **Authorize application** form, authorize Kanboard by clicking on the button **Accept** -4. Finally, you are redirected to Kanboard and now your user account is linked to your GitHub account -5. During the process, Kanboard has updated your full name and your email address based on your GitHub profile, if either of those are publically available -6. Log out of Kanboard and you should be able to login directly with GitHub by clicking on the link **Login with my GitHub Account** - -Installation instructions -------------------------- - -### Setting up OAuth 2.0 - -If you know what you're doing, you can directly go to the ["Register a new OAuth application"](https://github.com/settings/applications/new) site, set everything up and skip to [Setting up Kanboard](#setting-up-kanboard) below. - -Summarizing the [official GitHub documentation](https://developer.github.com/guides/basics-of-authentication/#registering-your-app): - -- Open your [**Settings**](https://github.com/settings), select [**Applications**](https://github.com/settings/applications) from the sidebar and click on [**Register new application**](https://github.com/settings/applications/new) on the top, next to where it says **Developer applications** -- Fill out the form with whatever values you like, only the **Authorization callback URL** _must_ be: **http://YOUR_SERVER/?controller=user&action=gitHub** - -### Setting up Kanboard - -Either create a new `config.php` file or copy and rename the `config.default.php` file and set the following values: - -```php -// Enable/disable GitHub authentication -define('GITHUB_AUTH', true); - -// GitHub client id (Copy it from your settings -> Applications -> Developer applications) -define('GITHUB_CLIENT_ID', 'YOUR_GITHUB_CLIENT_ID'); - -// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications) -define('GITHUB_CLIENT_SECRET', 'YOUR_GITHUB_CLIENT_SECRET'); - -``` - -Notes ------ -**Important:** _*Never*_ store your GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET in GitHub or somewhere with full public access in general! - -Kanboard uses these information from your public GitHub profile: - -- Full name -- Public email address -- GitHub unique id - -The GitHub unique id is used to link the local user account and the GitHub account. diff --git a/sources/docs/google-authentication.markdown b/sources/docs/google-authentication.markdown deleted file mode 100644 index 033cf4b..0000000 --- a/sources/docs/google-authentication.markdown +++ /dev/null @@ -1,65 +0,0 @@ -Google Authentication -===================== - -Requirements ------------- - -- OAuth Google API credentials (available in the Google Developer Console) - -How does this work? -------------------- - -- The Google authentication in Kanboard use the OAuth 2.0 protocol -- Any user account in Kanboard can be linked to a Google Account -- When a Kanboard user account is linked to Google, you can login with one click - -Procedure to link a Google Account ----------------------------------- - -1. The first step is to link an existing user account to a Google Account -2. Login with the desired user -3. Go to the **Edit user** page and click on the link **Link my Google Account** -4. You are redirected to the **Google Consent screen**, authorize Kanboard by clicking on the button **Accept** -5. Finally, you are redirected to Kanboard and now your user account is linked to your Google account -6. During the process, Kanboard have updated your full name and your email address based on your Google profile -7. Log out and you should be able to login directly with Google by clicking on the link **Login with my Google Account** - -Installation instructions -------------------------- - -### Setting up OAuth 2.0 - -Follow the [official Google documentation](https://developers.google.com/accounts/docs/OAuth2Login#appsetup), in summary: - -- Go to the [Developer Console](https://console.developers.google.com) -- On the sidebar, click on **Credentials** and choose **Create a new Client Id** -- Fill the form, the redirect URL must be: **http://YOUR_SERVER/?controller=user&action=google** - -### Setting up Kanboad - -Create a custom `config.php` file or copy the `config.default.php` file: - -```php -= 5.3.3 -- PHP extensions required: mbstring and pdo_sqlite (don't forget to enable extensions) -- A web browser with HTML5 drag and drop support - -From the archive ----------------- - -1. You must have a web server with PHP installed -2. Download the source code and copy the directory `kanboard` where you want -3. Check if the directory `data` is writeable (Kanboard stores everything inside a Sqlite database) -4. With your browser go to -5. The default login and password is **admin/admin** -6. Start to use the software -7. Don't forget to change your password! - -From the repository -------------------- - -1. `git clone https://github.com/fguillot/kanboard.git` -2. Go to the third step just above - -Security --------- - -- Don't forget to change the default user/password -- Don't allow everybody to access to the directory `data` from the URL. There is already a `.htaccess` for Apache but nothing for Nginx. diff --git a/sources/docs/ldap-authentication.markdown b/sources/docs/ldap-authentication.markdown deleted file mode 100644 index 0c4a572..0000000 --- a/sources/docs/ldap-authentication.markdown +++ /dev/null @@ -1,194 +0,0 @@ -LDAP authentication -=================== - -Requirements ------------- - -- LDAP extension for PHP -- LDAP server (By example: OpenLDAP or Microsoft Active Directory) - -Workflow --------- - -When the LDAP authentication is activated, the login process work like that: - -1. Try to authenticate the user by using the database -2. If the user is not found inside the database, a LDAP authentication is performed -3. If the LDAP authentication is successful, a local user is created automatically with no password and marked as LDAP user. - -Differences between a local user and a LDAP user are the following: - -- LDAP users have no local passwords -- LDAP users can't modify their password with the user interface -- By default, all LDAP users have no admin privileges -- To become administrator, a LDAP user must be promoted by another administrator - -The full name and the email address are automatically fetched from the LDAP server. - -Configuration -------------- - -You have to create a custom config file named `config.php` (you can also use the template `config.default.php`). -This file must be stored in the root directory of Kanboard. - -### Available configuration parameters - -```php -// Enable LDAP authentication (false by default) -define('LDAP_AUTH', false); - -// LDAP server hostname -define('LDAP_SERVER', ''); - -// LDAP server port (389 by default) -define('LDAP_PORT', 389); - -// By default, require certificate to be verified for ldaps:// style URL. Set to false to skip the verification. -define('LDAP_SSL_VERIFY', true); - -// LDAP bind type: "anonymous", "user" (use the given user/password from the form) and "proxy" (a specific user to browse the LDAP directory) -define('LDAP_BIND_TYPE', 'anonymous'); - -// LDAP username to connect with. null for anonymous bind (by default). -// Or for user bind type, you can use a pattern like that %s@kanboard.local -define('LDAP_USERNAME', null); - -// LDAP password to connect with. null for anonymous bind (by default). -define('LDAP_PASSWORD', null); - -// LDAP account base, i.e. root of all user account -// Example: ou=People,dc=example,dc=com -define('LDAP_ACCOUNT_BASE', ''); - -// LDAP query pattern to use when searching for a user account -// Example for ActiveDirectory: '(&(objectClass=user)(sAMAccountName=%s))' -// Example for OpenLDAP: 'uid=%s' -define('LDAP_USER_PATTERN', ''); - -// Name of an attribute of the user account object which should be used as the full name of the user. -define('LDAP_ACCOUNT_FULLNAME', 'displayname'); - -// Name of an attribute of the user account object which should be used as the email of the user. -define('LDAP_ACCOUNT_EMAIL', 'mail'); -``` - -### LDAP bind type - -There is 3 possible ways to browse the LDAP directory: - -#### Anonymous browsing - -```php -define('LDAP_BIND_TYPE', 'anonymous'); -define('LDAP_USERNAME', null); -define('LDAP_PASSWORD', null); -``` - -This is the default value but some LDAP servers don't allow that. - -#### Proxy user - -A specific user is used to browse the LDAP directory. -By example, Novell eDirectory use that method. - -```php -define('LDAP_BIND_TYPE', 'proxy'); -define('LDAP_USERNAME', 'my proxy user'); -define('LDAP_PASSWORD', 'my proxy password'); -``` - -#### User credentials - -This method use the credentials provided by the end-user. -By example, Microsoft Active Directory doesn't allow anonymous browsing by default and if you don't want to use a proxy user you can use this method. - -```php -define('LDAP_BIND_TYPE', 'user'); -define('LDAP_USERNAME', '%s@mydomain.local'); -define('LDAP_PASSWORD', null); -``` - -Here, the `LDAP_USERNAME` is use to define a replacement pattern: - -```php -define('LDAP_USERNAME', '%s@mydomain.local'); - -// Another way to do the same: - -define('LDAP_USERNAME', 'MYDOMAIN\\%s'); -``` - -### Example for Microsoft Active Directory - -Let's say we have a domain `KANBOARD` (kanboard.local) and the primary controller is `myserver.kanboard.local`. -Microsoft Active Directory doesn't allow anonymous binding by default. - -First example with a proxy user: - -```php - kanboard.pem -``` - -Copy the certificates in a new directory: - -```bash -mkdir /etc/nginx/ssl -cp kanboard.pem /etc/nginx/ssl -cp kanboard.key.nopass /etc/nginx/ssl -chmod 400 /etc/nginx/ssl/* -``` - -Configure Nginx ---------------- - -Now, we can customize our installation, start to modify the main configuration file `/etc/nginx/nginx.conf`: - -```nginx -user www-data; -worker_processes auto; -pid /run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - server_tokens off; - - # SSL shared cache between workers - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - # We disable weak protocols and ciphers - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_ciphers HIGH:!SSLv2:!MEDIUM:!LOW:!EXP:!RC4:!DSS:!aNULL:@STRENGTH; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - # We enable the Gzip compression for some mime types - gzip on; - gzip_disable "msie6"; - gzip_vary on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} -``` - -Create a new virtual host for Kanboard `/etc/nginx/sites-available/kanboard` - - -```nginx -server { - # We also enable the SPDY protocol - listen 443 ssl spdy; - - # Our SSL certificate - ssl on; - ssl_certificate /etc/nginx/ssl/kanboard.pem; - ssl_certificate_key /etc/nginx/ssl/kanboard.key.nopass; - - # You can change the default root directory here - root /usr/share/nginx/html; - - index index.php; - - # Your domain name - server_name localhost; - - # The maximum body size, useful for file uploads - client_max_body_size 10M; - - location / { - try_files $uri $uri/ =404; - } - - error_page 404 /404.html; - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - - # PHP-FPM configuration - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php5-fpm.sock; - fastcgi_index index.php; - include fastcgi.conf; - } - - # Deny access to the directory data - location ~* /data { - deny all; - return 404; - } - - # Deny access to .htaccess - location ~ /\.ht { - deny all; - return 404; - } -} -``` - -Now it's time to test our setup - -```bash -# Disable the default virtual host -sudo unlink /etc/nginx/sites-enabled/default - -# Add our default virtual host -sudo ln -s /etc/nginx/sites-available/kanboard /etc/nginx/sites-enabled/kanboard - -# Check the config file -sudo nginx -t -nginx: the configuration file /etc/nginx/nginx.conf syntax is ok -nginx: configuration file /etc/nginx/nginx.conf test is successful - -# Restart nginx -sudo service nginx restart -``` - -Kanboard Installation ---------------------- - -You can install Kanboard in a subdirectory or not, it's up to you. - -```bash -cd /usr/share/nginx/html -sudo wget http://kanboard.net/kanboard-latest.zip -sudo unzip kanboard-latest.zip -sudo chown -R www-data:www-data kanboard/data -sudo rm kanboard-latest.zip -``` - -Now, you should be able to use Kanboard with your web browser. diff --git a/sources/docs/postgresql-configuration.markdown b/sources/docs/postgresql-configuration.markdown deleted file mode 100644 index 04c6c2e..0000000 --- a/sources/docs/postgresql-configuration.markdown +++ /dev/null @@ -1,38 +0,0 @@ -Postgresql configuration -======================== - -By default, Kanboard use Sqlite to store its data but it's also possible to use Postgresql. - -Requirements ------------- - -- A Postgresql server already installed and configured -- The PHP extension `pdo_pgsql` installed (Debian/Ubuntu: `apt-get install php5-pgsql`) - -Configuration -------------- - -### Create an empty database with the command `pgsql`: - -```sql -CREATE DATABASE kanboard; -``` - -### Create a config file - -Inside our config file write those lines: - -```php - - -``` - -### Result - -[My link title](http://kanboard.net/) - - - -Source code ------------ - -### Inline code - -Use a backtick. - -``` -Execute this command: `tail -f /var/log/messages`. -``` - -### Result - -Execute this command: `tail -f /var/log/messages`. - -### Code blocks - -Use 3 backticks with eventually the language name. - -
-```php
-<?php
-
-phpinfo();
-
-?>
-```
-
-
- -### Result - -``` - -``` - -Titles ------- - -### Source - -``` -# Title level 1 - -## Title level 2 - -### Title level 3 -``` diff --git a/sources/docs/tests.markdown b/sources/docs/tests.markdown deleted file mode 100644 index 0a80aae..0000000 --- a/sources/docs/tests.markdown +++ /dev/null @@ -1,180 +0,0 @@ -How to run units and functionals tests? -======================================= - -[PHPUnit](https://phpunit.de/) is used to run automatic tests on Kanboard. - -You can run tests across different databases (Sqlite, Mysql and Postgresql) to be sure that the result is the same everywhere. - -Requirements ------------- - -- Linux/Unix machine -- PHP command line -- PHPUnit installed -- Mysql and Postgresql (optional) - -Install the latest version of PHPUnit -------------------------------------- - -Simply download the PHPUnit PHAR et copy the file somewhere in your `$PATH`: - -```bash -wget https://phar.phpunit.de/phpunit.phar -chmod +x phpunit.phar -sudo mv phpunit.phar /usr/local/bin/phpunit -phpunit --version -PHPUnit 4.2.6 by Sebastian Bergmann. -``` - -Running unit tests ------------------- - -### Testing with Sqlite - -Sqlite tests use a in-memory database, nothing is written on the filesystem. - -The config file is `tests/units.sqlite.xml`. -From your Kanboard directory, run the command `phpunit -c tests/units.sqlite.xml`. - -Example: - -```bash -phpunit -c tests/units.sqlite.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.sqlite.xml - -................................................................. 65 / 74 ( 87%) -......... - -Time: 9.05 seconds, Memory: 17.75Mb - -OK (74 tests, 6145 assertions) -``` - -### Testing with Mysql - -You must have Mysql or MariaDb installed on localhost. - -By default, those credentials are used: - -- Hostname: **localhost** -- Username: **root** -- Password: nothing (blank) -- Database: **kanboard_unit_test** - -For each execution the database is dropped and created again. - -The config file is `tests/units.mysql.xml`. -From your Kanboard directory, run the command `phpunit -c tests/units.mysql.xml`. - -Example: - -```bash -phpunit -c tests/units.mysql.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.mysql.xml - -................................................................. 65 / 74 ( 87%) -......... - -Time: 49.77 seconds, Memory: 17.50Mb - -OK (74 tests, 6145 assertions) -``` - -### Testing with Postgresql - -You must have Postgresql installed on localhost. - -By default, those credentials are used: - -- Hostname: **localhost** -- Username: **postgres** -- Password: **postgres** -- Database: **kanboard_unit_test** - -Be sure to allow the user `postgres` to create and drop databases. -For each execution the database is dropped and created again. - -The config file is `tests/units.postgres.xml`. -From your Kanboard directory, run the command `phpunit -c tests/units.postgres.xml`. - -Example: - -```bash -phpunit -c tests/units.postgres.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.postgres.xml - -................................................................. 65 / 74 ( 87%) -......... - -Time: 52.66 seconds, Memory: 17.50Mb - -OK (74 tests, 6145 assertions) -``` - -Running functionals tests -------------------------- - -Actually only the API calls are tested. - -Real HTTP calls are made with those tests. -So a local instance of Kanboard is necessary and must listen on `http://localhost:8000`. - -Don't forget that all data will be removed/altered by the testsuite. -Moreover the script will reset and set a new API key. - -1. Start a local instance of Kanboard `php -S 127.0.0.1:8000` -2. Run the testsuite from another terminal - -The same method as above is used to run tests across different databases: - -- Sqlite: `phpunit -c tests/functionals.sqlite.xml` -- Mysql: `phpunit -c tests/functionals.mysql.xml` -- Postgresql: `phpunit -c tests/functionals.postgres.xml` - -Example: - -```bash -phpunit -c tests/functionals.sqlite.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/functionals.sqlite.xml - -.......................................... - -Time: 1.72 seconds, Memory: 4.25Mb - -OK (42 tests, 160 assertions) -``` - -Continuous Integration with Travis ----------------------------------- - -After each commit pushed on the main repository, unit tests are executed across 4 different major versions of PHP. - -The Travis config file `.travis.yml` is located on the root directory of Kanboard: - -```yaml -language: php - -php: - - "5.6" - - "5.5" - - "5.4" - - "5.3" - -before_script: wget https://phar.phpunit.de/phpunit.phar -script: php phpunit.phar -c tests/units.sqlite.xml -``` - -As you can see, tests are executed with PHP 5.3, 5.4, 5.5 and 5.6. -However, only Sqlite unit tests are executed on Travis. diff --git a/sources/docs/translations.markdown b/sources/docs/translations.markdown deleted file mode 100644 index ec7162b..0000000 --- a/sources/docs/translations.markdown +++ /dev/null @@ -1,28 +0,0 @@ -Translations -============ - -How to translate Kanboard to a new language? --------------------------------------------- - -- Translations are stored inside the directory `app/Locales` -- There is sub-directory for each language, by example for the French we have `fr_FR`, Italian `it_IT` etc... -- A translation is a PHP file that return an Array with a key-value pairs -- The key is the original text in english and the value is the translation for the corresponding language -- **French translations are always up to date** -- Always use the last version (branch master) - -### Create a new translation: - -1. Make a new directory: `app/Locales/xx_XX` by example `app/Locales/fr_CA` for French Canadian -2. Create a new file for the translation: `app/Locales/xx_XX/translations.php` -3. Use the content of the French locales and replace the values -4. Inside the file `app/Model/Config.php`, add a new entry for your translation inside the function `getLanguages()` -5. Check with your local installation of Kanboard if everything is ok -6. Send a pull-request with Github - -How to update an existing translation? --------------------------------------- - -1. Open the translation file `app/Locales/xx_XX/translations.php` -2. Missing translations are commented with `//` and the values are empty, just fill blank and remove comments -3. Check with your local installation of Kanboard and send a pull-request diff --git a/sources/docs/ubuntu-installation.markdown b/sources/docs/ubuntu-installation.markdown deleted file mode 100644 index 5f9ee65..0000000 --- a/sources/docs/ubuntu-installation.markdown +++ /dev/null @@ -1,28 +0,0 @@ -How to install Kanboard on Ubuntu? -================================== - -Ubuntu 14.04 LTS ----------------- - -Install Apache and PHP: - -```bash -sudo apt-get update -sudo apt-get install -y php5 php5-sqlite unzip -``` - -In case your webserver was running restart to make sure the php modules are reloaded - -```bash -service apache2 restart -``` - -Install Kanboard: - -```bash -cd /var/www/html -sudo wget http://kanboard.net/kanboard-latest.zip -sudo unzip kanboard-latest.zip -sudo chown -R www-data:www-data kanboard/data -sudo rm kanboard-latest.zip -``` diff --git a/sources/docs/update.markdown b/sources/docs/update.markdown deleted file mode 100644 index d997f3e..0000000 --- a/sources/docs/update.markdown +++ /dev/null @@ -1,19 +0,0 @@ -Update -====== - -From the archive ----------------- - -1. Close your session (logout) -2. Rename your actual Kanboard directory (to keep a backup) -3. Uncompress the new archive and copy your database file `db.sqlite` in the directory `data` -4. Make the directory `data` writeable by the web server user -5. Login and check if everything is ok -6. Remove the old Kanboard directory - -From the repository -------------------- - -1. Close your session (logout) -2. `git pull` -3. Login and check if everything is ok diff --git a/sources/docs/usage-examples.markdown b/sources/docs/usage-examples.markdown deleted file mode 100644 index f2d457a..0000000 --- a/sources/docs/usage-examples.markdown +++ /dev/null @@ -1,67 +0,0 @@ -Usage examples -============== - -You can customize your boards according to your business activities: - -Software development --------------------- - -- Backlog -- Ready -- Work in progress -- To be validated -- Validated -- Deployed in production - -Bug tracking ------------- - -- Reported -- Confirmed -- Work in progress -- Tested -- Fixed - -Sales ------ - -- Leads -- Meeting -- Proposal -- Purchase - -Lean business management ------------------------- - -- Ideas -- Development -- Measure -- Analysis -- Done - -Recruiting process ------------------- - -- Job offers -- Candidates -- Phone screens -- Interviews -- Hires - -Online shops ------------- - -- Orders -- Packaging -- Ready to send -- Shipped - -Manufactory ------------ - -- Customer Orders -- Assembly -- Tests -- Packaging -- Ready to ship -- Shipped diff --git a/sources/docs/vagrant.markdown b/sources/docs/vagrant.markdown deleted file mode 100644 index d19e356..0000000 --- a/sources/docs/vagrant.markdown +++ /dev/null @@ -1,8 +0,0 @@ -How to test Kanboard with Vagrant? -================================== - -- Install Vagrant (http://www.vagrantup.com or apt-get install vagrant) -- Install VirtualBox (https://www.virtualbox.org/ or apt-get install virtualbox) -- Inside the root directory, and run the command `vagrant up` -- Go to http://localhost:8080/index.php -- Login with admin / admin diff --git a/sources/docs/webhooks.markdown b/sources/docs/webhooks.markdown deleted file mode 100644 index 1d46174..0000000 --- a/sources/docs/webhooks.markdown +++ /dev/null @@ -1,98 +0,0 @@ -Webhooks -======== - -Webhooks are useful to perform actions with external applications. - -- Webhooks can be used to create a task by calling a simple URL (You can also do that by using the API) -- An external URL can be called automatically when a task is created or modified - -How to create a task with a webhook? ------------------------------------- - -Firstly, you have to get the token from the settings page. After that, just call this url from anywhere: - -```bash -# Create a task for the default project inside the first column -curl "http://myserver/?controller=task&action=add&token=superSecretToken&title=mySuperTask" - -# Create a task to another project inside a specific column with the color red -curl "http://myserver/?controller=task&action=add&token=superSecretToken&title=task123&project_id=3&column_id=7&color_id=red" -``` - -### Available responses - -- When a task is created successfully, Kanboard return the message "OK" in plain text. -- However if the task creation fail, you will got a "FAILED" message. -- If the token is wrong, you got a "Not Authorized" message and a HTTP status code 401. - -### Available parameters - -Base URL: `http://YOUR_SERVER_HOSTNAME/?controller=task&action=add` - -- `token`: Token displayed on the settings page (required) -- `title`: Task title (required) -- `description`: Task description -- `color_id`: Supported colors are yellow, blue, green, purple, red, orange and grey -- `project_id`: Project id (Get the id from the projects page, mouse over on the project title) -- `owner_id`: Assignee (Get the user id from the users page, mouse over on the username) -- `column_id`: Column on the board (Get the column id from the projects page, mouse over on the column name) - -Only the token and the title parameters are mandatory. The different id can also be found in the database. - -How to call an external URL when a task is created or updated? --------------------------------------------------------------- - -- There is two events available: **task creation** and **task modification** -- External URLs can be defined on the settings page -- When an event is triggered Kanboard call automatically the predefined URL -- The task data encoded in JSON is sent with a POST HTTP request -- The webhook token is also sent as a query string parameter, so you can check if the request is not usurped, it's also better if you use HTTPS. -- **Your custom URL must answer in less than 1 second**, those requests are synchronous (PHP limitation) and that can slow down the application if your script is too slow! - -### Quick example with PHP - -Start by creating a basic PHP script `index.php`: - -```php - -``` - -Open a browser at `http://localhost/phpinfo.php` and you should see the current PHP settings. -If you got an error 500, something is not correctly done in your installation. - -Notes: - -- If you use PHP < 5.4, you have to enable the short tags in your php.ini -- Don't forget to enable the required php extensions: `pdo_sqlite` and `mbstring` -- If you got an error about "the library MSVCP110.dll is missing", you probably need to download the Visual C++ Redistributable for Visual Studio from the Microsoft website. - -### Kanboard installation - -- Download the zip file -- Uncompress the archive in `C:\inetpub\wwwroot\kanboard` by example -- Make sure the directory `data` is writable by the IIS user -- You are done, open your web browser to use Kanboard - -### Tested configuration - -- Windows 2008 R2 Standard Edition / IIS 7.5 / PHP 5.5.16 -- Windows 2012 Standard Edition / IIS 8.5 / PHP 5.3.29 diff --git a/sources/jsonrpc.php b/sources/jsonrpc.php index 1f3cf65..99d1480 100644 --- a/sources/jsonrpc.php +++ b/sources/jsonrpc.php @@ -2,10 +2,12 @@ require __DIR__.'/app/common.php'; -use Core\Translator; use JsonRPC\Server; use Model\Project; +use Model\ProjectPermission; use Model\Task; +use Model\TaskFinder; +use Model\TaskValidator; use Model\User; use Model\Config; use Model\Category; @@ -17,8 +19,14 @@ use Model\Webhook; use Model\Notification; $config = new Config($registry); +$config->setupTranslations(); +$config->setupTimezone(); + $project = new Project($registry); +$projectPermission = new ProjectPermission($registry); $task = new Task($registry); +$taskFinder = new TaskFinder($registry); +$taskValidator = new TaskValidator($registry); $user = new User($registry); $category = new Category($registry); $comment = new Comment($registry); @@ -33,14 +41,9 @@ $project->attachEvents(); $webhook->attachEvents(); $notification->attachEvents(); -// Load translations -$language = $config->get('language', 'en_US'); -if ($language !== 'en_US') Translator::load($language); - $server = new Server; $server->authentication(array('jsonrpc' => $config->get('api_token'))); - /** * Project procedures */ @@ -142,23 +145,23 @@ $server->register('removeColumn', function($column_id) use ($board) { /** * Project permissions procedures */ -$server->register('getAllowedUsers', function($project_id) use ($project) { - return $project->getUsersList($project_id, false, false); +$server->register('getAllowedUsers', function($project_id) use ($projectPermission) { + return $projectPermission->getUsersList($project_id, false, false); }); -$server->register('revokeUser', function($project_id, $user_id) use ($project) { - return $project->revokeUser($project_id, $user_id); +$server->register('revokeUser', function($project_id, $user_id) use ($project, $projectPermission) { + return $projectPermission->revokeUser($project_id, $user_id); }); -$server->register('allowUser', function($project_id, $user_id) use ($project) { - return $project->allowUser($project_id, $user_id); +$server->register('allowUser', function($project_id, $user_id) use ($project, $projectPermission) { + return $projectPermission->allowUser($project_id, $user_id); }); /** * Task procedures */ -$server->register('createTask', function($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, $date_due = '', $description = '', $category_id = 0, $score = 0) use ($task) { +$server->register('createTask', function($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, $date_due = '', $description = '', $category_id = 0, $score = 0) use ($task, $taskValidator) { $values = array( 'title' => $title, @@ -173,19 +176,19 @@ $server->register('createTask', function($title, $project_id, $color_id = '', $c 'score' => $score, ); - list($valid,) = $task->validateCreation($values); + list($valid,) = $taskValidator->validateCreation($values); return $valid && $task->create($values) !== false; }); -$server->register('getTask', function($task_id) use ($task) { - return $task->getById($task_id); +$server->register('getTask', function($task_id) use ($taskFinder) { + return $taskFinder->getById($task_id); }); -$server->register('getAllTasks', function($project_id, $status) use ($task) { - return $task->getAll($project_id, $status); +$server->register('getAllTasks', function($project_id, $status) use ($taskFinder) { + return $taskFinder->getAll($project_id, $status); }); -$server->register('updateTask', function($id, $title = null, $project_id = null, $color_id = null, $column_id = null, $owner_id = null, $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null) use ($task) { +$server->register('updateTask', function($id, $title = null, $project_id = null, $color_id = null, $column_id = null, $owner_id = null, $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null) use ($task, $taskValidator) { $values = array( 'id' => $id, @@ -207,7 +210,7 @@ $server->register('updateTask', function($id, $title = null, $project_id = null, } } - list($valid) = $task->validateModification($values); + list($valid) = $taskValidator->validateApiModification($values); return $valid && $task->update($values); }); diff --git a/sources/kanboard b/sources/kanboard index 2c3ee7a..b82bff0 100644 --- a/sources/kanboard +++ b/sources/kanboard @@ -5,19 +5,15 @@ require __DIR__.'/app/common.php'; use Core\Cli; use Core\Tool; -use Core\Translator; use Model\Config; use Model\Task; +use Model\TaskFinder; +use Model\TaskExport; use Model\Notification; $config = new Config($registry); - -// Load translations -$language = $config->get('language', 'en_US'); -if ($language !== 'en_US') Translator::load($language); - -// Set timezone -date_default_timezone_set($config->get('timezone', 'UTC')); +$config->setupTranslations(); +$config->setupTimezone(); // Setup CLI $cli = new Cli; @@ -40,8 +36,8 @@ $cli->register('export-csv', function() use ($cli, $registry) { $start_date = $GLOBALS['argv'][3]; $end_date = $GLOBALS['argv'][4]; - $taskModel = new Task($registry); - $data = $taskModel->export($project_id, $start_date, $end_date); + $taskExport = new TaskExport($registry); + $data = $taskExport->export($project_id, $start_date, $end_date); if (is_array($data)) { Tool::csv($data); @@ -52,7 +48,7 @@ $cli->register('export-csv', function() use ($cli, $registry) { $cli->register('send-notifications-due-tasks', function() use ($cli, $registry) { $notificationModel = new Notification($registry); - $taskModel = new Task($registry); + $taskModel = new TaskFinder($registry); $tasks = $taskModel->getOverdueTasks(); // Group tasks by project diff --git a/sources/scripts/.htaccess b/sources/scripts/.htaccess deleted file mode 100644 index 14249c5..0000000 --- a/sources/scripts/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Deny from all \ No newline at end of file diff --git a/sources/scripts/api-client.php b/sources/scripts/api-client.php deleted file mode 100644 index 0fb4511..0000000 --- a/sources/scripts/api-client.php +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env php - '.PHP_EOL); -} - -$client = new JsonRPC\Client($argv[1], 5, true); -$client->authentication('jsonrpc', $argv[2]); - - -$client->createProject('Test API'); - - -$r = $client->getAllProjects(); - -var_dump($r); - diff --git a/sources/scripts/create-random-tasks.php b/sources/scripts/create-random-tasks.php deleted file mode 100644 index 4d66517..0000000 --- a/sources/scripts/create-random-tasks.php +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env php - 'Task #'.$i.'-'.$column_id, - 'project_id' => 1, - 'column_id' => $column_id, - 'owner_id' => rand(0, 1), - 'color_id' => rand(0, 1) === 0 ? 'green' : 'purple', - 'score' => rand(0, 21), - ); - - $taskModel->create($task); - } -} diff --git a/sources/scripts/make-archive.sh b/sources/scripts/make-archive.sh deleted file mode 100644 index 2518f02..0000000 --- a/sources/scripts/make-archive.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -VERSION=$1 -APP="kanboard" - -cd /tmp -rm -rf /tmp/$APP /tmp/$APP-*.zip 2>/dev/null - -git clone --depth 1 https://github.com/fguillot/$APP.git - -rm -rf $APP/data/*.sqlite \ - $APP/.git $APP/.gitignore \ - $APP/scripts \ - $APP/tests \ - $APP/Vagrantfile \ - $APP/.*.yml \ - $APP/README.markdown \ - $APP/docs - -sed -i.bak s/master/$VERSION/g $APP/app/constants.php && rm -f $APP/app/*.bak -zip -r $APP-$VERSION.zip $APP - -mv $APP-$VERSION.zip ~/Devel/websites/$APP - -cd ~/Devel/websites/$APP/ -unlink $APP-latest.zip -ln -s $APP-$VERSION.zip $APP-latest.zip - -rm -rf /tmp/$APP 2>/dev/null diff --git a/sources/scripts/make-tag.sh b/sources/scripts/make-tag.sh deleted file mode 100644 index 108444f..0000000 --- a/sources/scripts/make-tag.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -VERSION=$1 - -git tag -a v$VERSION -m "Version $VERSION" -git push origin v$VERSION diff --git a/sources/scripts/sync-locales.php b/sources/scripts/sync-locales.php deleted file mode 100644 index 00d6c89..0000000 --- a/sources/scripts/sync-locales.php +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env php - $value) { - - if (isset($outdated[$key])) { - //$output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $value)."',\n"; - $output .= " '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $outdated[$key])."',\n"; - } - else { - //$output .= " // '".str_replace("'", "\'", $key)."' => '".str_replace("'", "\'", $value)."',\n"; - $output .= " // '".str_replace("'", "\'", $key)."' => '',\n"; - } - } - - $output .= ");\n"; - return $output; -} - - -foreach (new DirectoryIterator('app/Locales') as $fileInfo) { - - if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== $reference_lang) { - - $filename = 'app/Locales/'.$fileInfo->getFilename().'/translations.php'; - - echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL; - - file_put_contents($filename, update_missing_locales($reference, $filename)); - } -} diff --git a/sources/tests/.htaccess b/sources/tests/.htaccess deleted file mode 100644 index 14249c5..0000000 --- a/sources/tests/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Deny from all \ No newline at end of file diff --git a/sources/tests/functionals.mysql.xml b/sources/tests/functionals.mysql.xml deleted file mode 100644 index f667caf..0000000 --- a/sources/tests/functionals.mysql.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - functionals - - - - - - - - - - - - \ No newline at end of file diff --git a/sources/tests/functionals.postgres.xml b/sources/tests/functionals.postgres.xml deleted file mode 100644 index 38904d1..0000000 --- a/sources/tests/functionals.postgres.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - functionals - - - - - - - - - - - - \ No newline at end of file diff --git a/sources/tests/functionals.sqlite.xml b/sources/tests/functionals.sqlite.xml deleted file mode 100644 index bf5d411..0000000 --- a/sources/tests/functionals.sqlite.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - functionals - - - - - - - - - \ No newline at end of file diff --git a/sources/tests/functionals/ApiTest.php b/sources/tests/functionals/ApiTest.php deleted file mode 100644 index 13c25a7..0000000 --- a/sources/tests/functionals/ApiTest.php +++ /dev/null @@ -1,559 +0,0 @@ -exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME); - $pdo = new PDO('mysql:host='.DB_HOSTNAME.';dbname='.DB_NAME, DB_USERNAME, DB_PASSWORD); - } - else if (DB_DRIVER === 'postgres') { - $pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); - $pdo->exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME); - $pdo = new PDO('pgsql:host='.DB_HOSTNAME.';dbname='.DB_NAME, DB_USERNAME, DB_PASSWORD); - } - - setup_db(); - - $pdo->exec("UPDATE config SET api_token='".API_KEY."'"); - $pdo = null; - } - - public function setUp() - { - $this->client = new JsonRPC\Client(API_URL); - $this->client->authentication('jsonrpc', API_KEY); - //$this->client->debug = true; - } - - private function getTaskId() - { - $tasks = $this->client->getAllTasks(1, 1); - $this->assertNotEmpty($tasks); - $this->assertEquals(1, count($tasks)); - - return $tasks[0]['id']; - } - - public function testRemoveAll() - { - $projects = $this->client->getAllProjects(); - - if ($projects) { - foreach ($projects as $project) { - $this->assertTrue($this->client->removeProject($project['id'])); - } - } - } - - public function testCreateProject() - { - $this->assertTrue($this->client->createProject('API test')); - } - - public function testGetProjectById() - { - $project = $this->client->getProjectById(1); - $this->assertNotEmpty($project); - $this->assertEquals(1, $project['id']); - } - - public function testUpdateProject() - { - $project = $this->client->getProjectById(1); - $this->assertNotEmpty($project); - $this->assertTrue($this->client->execute('updateProject', array('id' => 1, 'name' => 'API test 2', 'is_active' => 0))); - - $project = $this->client->getProjectById(1); - $this->assertEquals('API test 2', $project['name']); - $this->assertEquals(0, $project['is_active']); - - $this->assertTrue($this->client->execute('updateProject', array('id' => 1, 'name' => 'API test', 'is_active' => 1))); - - $project = $this->client->getProjectById(1); - $this->assertEquals('API test', $project['name']); - $this->assertEquals(1, $project['is_active']); - } - - public function testGetBoard() - { - $board = $this->client->getBoard(1); - $this->assertTrue(is_array($board)); - $this->assertEquals(4, count($board)); - } - - public function testGetColumns() - { - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals(4, count($columns)); - $this->assertEquals('Done', $columns[3]['title']); - } - - public function testMoveColumnUp() - { - $this->assertTrue($this->client->moveColumnUp(1, 4)); - - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals('Done', $columns[2]['title']); - $this->assertEquals('Work in progress', $columns[3]['title']); - } - - public function testMoveColumnDown() - { - $this->assertTrue($this->client->moveColumnDown(1, 4)); - - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals('Work in progress', $columns[2]['title']); - $this->assertEquals('Done', $columns[3]['title']); - } - - public function testUpdateColumn() - { - $this->assertTrue($this->client->updateColumn(4, 'Boo', 2)); - - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals('Boo', $columns[3]['title']); - $this->assertEquals(2, $columns[3]['task_limit']); - } - - public function testAddColumn() - { - $this->assertTrue($this->client->addColumn(1, 'New column')); - - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals(5, count($columns)); - $this->assertEquals('New column', $columns[4]['title']); - } - - public function testRemoveColumn() - { - $this->assertTrue($this->client->removeColumn(5)); - - $columns = $this->client->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals(4, count($columns)); - } - - public function testCreateTask() - { - $task = array( - 'title' => 'Task #1', - 'color_id' => 'blue', - 'owner_id' => 1, - 'project_id' => 1, - 'column_id' => 2, - ); - - $this->assertTrue($this->client->execute('createTask', $task)); - } - - /** - * @expectedException BadFunctionCallException - */ - public function testCreateTaskWithBadParams() - { - $task = array( - 'title' => 'Task #1', - 'color_id' => 'blue', - 'owner_id' => 1, - ); - - $this->client->createTask($task); - } - - public function testGetTask() - { - $task = $this->client->getTask(1); - - $this->assertNotFalse($task); - $this->assertTrue(is_array($task)); - $this->assertEquals('Task #1', $task['title']); - } - - public function testGetAllTasks() - { - $tasks = $this->client->getAllTasks(1, 1); - - $this->assertNotFalse($tasks); - $this->assertTrue(is_array($tasks)); - $this->assertEquals('Task #1', $tasks[0]['title']); - - $tasks = $this->client->getAllTasks(2, 0); - - $this->assertNotFalse($tasks); - $this->assertTrue(is_array($tasks)); - $this->assertEmpty($tasks); - } - - public function testUpdateTask() - { - $task = $this->client->getTask(1); - $task['color_id'] = 'green'; - $task['column_id'] = 1; - $task['description'] = 'test'; - $task['date_due'] = ''; - - $this->assertTrue($this->client->execute('updateTask', $task)); - } - - public function testRemoveTask() - { - $this->assertTrue($this->client->removeTask(1)); - } - - public function testRemoveUsers() - { - $users = $this->client->getAllUsers(); - $this->assertNotFalse($users); - $this->assertNotEmpty($users); - - foreach ($users as $user) { - if ($user['id'] > 1) { - $this->assertTrue($this->client->removeUser($user['id'])); - } - } - } - - public function testCreateUser() - { - $user = array( - 'username' => 'toto', - 'name' => 'Toto', - 'password' => '123456', - ); - - $this->assertTrue($this->client->execute('createUser', $user)); - } - - /** - * @expectedException BadFunctionCallException - */ - public function testCreateUserWithBadParams() - { - $user = array( - 'name' => 'Titi', - 'password' => '123456', - ); - - $this->assertNull($this->client->execute('createUser', $user)); - } - - public function testGetUser() - { - $user = $this->client->getUser(2); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('toto', $user['username']); - - $this->assertNull($this->client->getUser(2222)); - } - - public function testUpdateUser() - { - $user = array(); - $user['id'] = 2; - $user['username'] = 'titi'; - $user['name'] = 'Titi'; - - $this->assertTrue($this->client->execute('updateUser', $user)); - - $user = $this->client->getUser(2); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('titi', $user['username']); - $this->assertEquals('Titi', $user['name']); - - $user = array(); - $user['id'] = 2; - $user['email'] = 'titi@localhost'; - - $this->assertTrue($this->client->execute('updateUser', $user)); - - $user = $this->client->getUser(2); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('titi@localhost', $user['email']); - } - - public function testGetAllowedUsers() - { - $users = $this->client->getAllowedUsers(1); - $this->assertNotFalse($users); - $this->assertEquals(array(1 => 'admin', 2 => 'Titi'), $users); - } - - public function testAllowedUser() - { - $this->assertTrue($this->client->allowUser(1, 2)); - - $users = $this->client->getAllowedUsers(1); - $this->assertNotFalse($users); - $this->assertEquals(array(2 => 'Titi'), $users); - } - - public function testRevokeUser() - { - $this->assertTrue($this->client->revokeUser(1, 2)); - - $users = $this->client->getAllowedUsers(1); - $this->assertNotFalse($users); - $this->assertEquals(array(1 => 'admin', 2 => 'Titi'), $users); - } - - public function testCreateComment() - { - $task = array( - 'title' => 'Task with comment', - 'color_id' => 'red', - 'owner_id' => 1, - 'project_id' => 1, - 'column_id' => 1, - ); - - $this->assertTrue($this->client->execute('createTask', $task)); - - $tasks = $this->client->getAllTasks(1, 1); - $this->assertNotEmpty($tasks); - $this->assertEquals(1, count($tasks)); - - $comment = array( - 'task_id' => $tasks[0]['id'], - 'user_id' => 2, - 'content' => 'boo', - ); - - $this->assertTrue($this->client->execute('createComment', $comment)); - } - - public function testGetComment() - { - $comment = $this->client->getComment(1); - $this->assertNotFalse($comment); - $this->assertNotEmpty($comment); - $this->assertEquals(2, $comment['user_id']); - $this->assertEquals('boo', $comment['comment']); - } - - public function testUpdateComment() - { - $comment = array(); - $comment['id'] = 1; - $comment['content'] = 'test'; - - $this->assertTrue($this->client->execute('updateComment', $comment)); - - $comment = $this->client->getComment(1); - $this->assertEquals('test', $comment['comment']); - } - - public function testGetAllComments() - { - $task_id = $this->getTaskId(); - - $comment = array( - 'task_id' => $task_id, - 'user_id' => 1, - 'content' => 'blabla', - ); - - $this->assertTrue($this->client->execute('createComment', $comment)); - - $comments = $this->client->getAllComments($task_id); - $this->assertNotFalse($comments); - $this->assertNotEmpty($comments); - $this->assertTrue(is_array($comments)); - $this->assertEquals(2, count($comments)); - } - - public function testRemoveComment() - { - $task_id = $this->getTaskId(); - - $comments = $this->client->getAllComments($task_id); - $this->assertNotFalse($comments); - $this->assertNotEmpty($comments); - $this->assertTrue(is_array($comments)); - - foreach ($comments as $comment) { - $this->assertTrue($this->client->removeComment($comment['id'])); - } - - $comments = $this->client->getAllComments($task_id); - $this->assertNotFalse($comments); - $this->assertEmpty($comments); - $this->assertTrue(is_array($comments)); - } - - public function testCreateSubtask() - { - $subtask = array( - 'task_id' => $this->getTaskId(), - 'title' => 'subtask #1', - ); - - $this->assertTrue($this->client->execute('createSubtask', $subtask)); - } - - public function testGetSubtask() - { - $subtask = $this->client->getSubtask(1); - $this->assertNotFalse($subtask); - $this->assertNotEmpty($subtask); - $this->assertEquals($this->getTaskId(), $subtask['task_id']); - $this->assertEquals(0, $subtask['user_id']); - $this->assertEquals('subtask #1', $subtask['title']); - } - - public function testUpdateSubtask() - { - $subtask = array(); - $subtask['id'] = 1; - $subtask['task_id'] = $this->getTaskId(); - $subtask['title'] = 'test'; - - $this->assertTrue($this->client->execute('updateSubtask', $subtask)); - - $subtask = $this->client->getSubtask(1); - $this->assertEquals('test', $subtask['title']); - } - - public function testGetAllSubtasks() - { - $subtask = array( - 'task_id' => $this->getTaskId(), - 'user_id' => 2, - 'title' => 'Subtask #2', - ); - - $this->assertTrue($this->client->execute('createSubtask', $subtask)); - - $subtasks = $this->client->getAllSubtasks($this->getTaskId()); - $this->assertNotFalse($subtasks); - $this->assertNotEmpty($subtasks); - $this->assertTrue(is_array($subtasks)); - $this->assertEquals(2, count($subtasks)); - } - - public function testRemoveSubtask() - { - $this->assertTrue($this->client->removeSubtask(1)); - - $subtasks = $this->client->getAllSubtasks($this->getTaskId()); - $this->assertNotFalse($subtasks); - $this->assertNotEmpty($subtasks); - $this->assertTrue(is_array($subtasks)); - $this->assertEquals(1, count($subtasks)); - } - - public function testMoveTaskPosition() - { - $task_id = $this->getTaskId(); - $this->assertTrue($this->client->moveTaskPosition(1, $task_id, 3, 1)); - - $task = $this->client->getTask($task_id); - $this->assertNotFalse($task); - $this->assertTrue(is_array($task)); - $this->assertEquals(1, $task['position']); - $this->assertEquals(3, $task['column_id']); - } - - public function testCategoryCreation() - { - $category = array( - 'name' => 'Category', - 'project_id' => 1, - ); - - $this->assertTrue($this->client->execute('createCategory', $category)); - - // Duplicate - - $category = array( - 'name' => 'Category', - 'project_id' => 1, - ); - - $this->assertFalse($this->client->execute('createCategory', $category)); - } - - /** - * @expectedException BadFunctionCallException - */ - public function testCategoryCreationWithBadParams() - { - // Missing project id - $category = array( - 'name' => 'Category', - ); - - $this->assertNull($this->client->execute('createCategory', $category)); - } - - public function testCategoryRead() - { - $category = $this->client->getCategory(1); - - $this->assertTrue(is_array($category)); - $this->assertNotEmpty($category); - $this->assertEquals(1, $category['id']); - $this->assertEquals('Category', $category['name']); - $this->assertEquals(1, $category['project_id']); - } - - public function testGetAllCategories() - { - $categories = $this->client->getAllCategories(1); - - $this->assertNotEmpty($categories); - $this->assertNotFalse($categories); - $this->assertTrue(is_array($categories)); - $this->assertEquals(1, count($categories)); - $this->assertEquals(1, $categories[0]['id']); - $this->assertEquals('Category', $categories[0]['name']); - $this->assertEquals(1, $categories[0]['project_id']); - } - - public function testCategoryUpdate() - { - $category = array( - 'id' => 1, - 'name' => 'Renamed category', - ); - - $this->assertTrue($this->client->execute('updateCategory', $category)); - - $category = $this->client->getCategory(1); - $this->assertTrue(is_array($category)); - $this->assertNotEmpty($category); - $this->assertEquals(1, $category['id']); - $this->assertEquals('Renamed category', $category['name']); - $this->assertEquals(1, $category['project_id']); - } - - public function testCategoryRemove() - { - $this->assertTrue($this->client->removeCategory(1)); - $this->assertFalse($this->client->removeCategory(1)); - $this->assertFalse($this->client->removeCategory(1111)); - } -} diff --git a/sources/tests/units.mysql.xml b/sources/tests/units.mysql.xml deleted file mode 100644 index 0e308a0..0000000 --- a/sources/tests/units.mysql.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - units - - - - - - - \ No newline at end of file diff --git a/sources/tests/units.postgres.xml b/sources/tests/units.postgres.xml deleted file mode 100644 index 0583c7f..0000000 --- a/sources/tests/units.postgres.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - units - - - - - - - - \ No newline at end of file diff --git a/sources/tests/units.sqlite.xml b/sources/tests/units.sqlite.xml deleted file mode 100644 index 9771a2a..0000000 --- a/sources/tests/units.sqlite.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - units - - - - - - - \ No newline at end of file diff --git a/sources/tests/units/AclTest.php b/sources/tests/units/AclTest.php deleted file mode 100644 index 7dff0a6..0000000 --- a/sources/tests/units/AclTest.php +++ /dev/null @@ -1,112 +0,0 @@ - array('action1', 'action3'), - ); - - $acl = new Acl($this->registry); - $this->assertTrue($acl->isAllowedAction($acl_rules, 'controller1', 'action1')); - $this->assertTrue($acl->isAllowedAction($acl_rules, 'controller1', 'action3')); - $this->assertFalse($acl->isAllowedAction($acl_rules, 'controller1', 'action2')); - $this->assertFalse($acl->isAllowedAction($acl_rules, 'controller2', 'action2')); - $this->assertFalse($acl->isAllowedAction($acl_rules, 'controller2', 'action3')); - } - - public function testIsAdmin() - { - $acl = new Acl($this->registry); - - $_SESSION = array(); - $this->assertFalse($acl->isAdminUser()); - - $_SESSION = array('user' => array()); - $this->assertFalse($acl->isAdminUser()); - - $_SESSION = array('user' => array('is_admin' => '1')); - $this->assertFalse($acl->isAdminUser()); - - $_SESSION = array('user' => array('is_admin' => false)); - $this->assertFalse($acl->isAdminUser()); - - $_SESSION = array('user' => array('is_admin' => '2')); - $this->assertFalse($acl->isAdminUser()); - - $_SESSION = array('user' => array('is_admin' => true)); - $this->assertTrue($acl->isAdminUser()); - } - - public function testIsUser() - { - $acl = new Acl($this->registry); - - $_SESSION = array(); - $this->assertFalse($acl->isRegularUser()); - - $_SESSION = array('user' => array()); - $this->assertFalse($acl->isRegularUser()); - - $_SESSION = array('user' => array('is_admin' => true)); - $this->assertFalse($acl->isRegularUser()); - - $_SESSION = array('user' => array('is_admin' => true)); - $this->assertFalse($acl->isRegularUser()); - - $_SESSION = array('user' => array('is_admin' => '2')); - $this->assertFalse($acl->isRegularUser()); - - $_SESSION = array('user' => array('is_admin' => false)); - $this->assertTrue($acl->isRegularUser()); - } - - public function testIsPageAllowed() - { - $acl = new Acl($this->registry); - - // Public access - $_SESSION = array(); - $this->assertFalse($acl->isPageAccessAllowed('user', 'create')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'save')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'remove')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'confirm')); - $this->assertFalse($acl->isPageAccessAllowed('app', 'index')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'index')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'login')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'check')); - $this->assertTrue($acl->isPageAccessAllowed('task', 'add')); - $this->assertTrue($acl->isPageAccessAllowed('board', 'readonly')); - - // Regular user - $_SESSION = array('user' => array('is_admin' => false)); - $this->assertFalse($acl->isPageAccessAllowed('user', 'create')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'save')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'remove')); - $this->assertFalse($acl->isPageAccessAllowed('user', 'confirm')); - $this->assertTrue($acl->isPageAccessAllowed('app', 'index')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'index')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'login')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'check')); - $this->assertTrue($acl->isPageAccessAllowed('task', 'add')); - $this->assertTrue($acl->isPageAccessAllowed('board', 'readonly')); - - // Admin user - $_SESSION = array('user' => array('is_admin' => true)); - $this->assertTrue($acl->isPageAccessAllowed('user', 'create')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'save')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'remove')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'confirm')); - $this->assertTrue($acl->isPageAccessAllowed('app', 'index')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'index')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'login')); - $this->assertTrue($acl->isPageAccessAllowed('user', 'check')); - $this->assertTrue($acl->isPageAccessAllowed('task', 'add')); - $this->assertTrue($acl->isPageAccessAllowed('board', 'readonly')); - } -} diff --git a/sources/tests/units/ActionTaskAssignColorCategoryTest.php b/sources/tests/units/ActionTaskAssignColorCategoryTest.php deleted file mode 100644 index 84334dc..0000000 --- a/sources/tests/units/ActionTaskAssignColorCategoryTest.php +++ /dev/null @@ -1,76 +0,0 @@ -registry)); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskAssignColorCategory(1, new Task($this->registry)); - $action->setParam('category_id', 1); - $action->setParam('color_id', 'blue'); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $c->create(array('name' => 'c1'))); - $this->assertEquals(2, $c->create(array('name' => 'c2'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'color_id' => 'green', 'category_id' => 2))); - - // We create an event but we don't do anything - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 1, - 'category_id' => 2, - 'position' => 2, - ); - - // Our event should NOT be executed - $this->assertFalse($action->execute($event)); - - // Our task should be assigned to the ategory_id=1 and have the green color - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(2, $task['category_id']); - $this->assertEquals('green', $task['color_id']); - - // We create an event to move the task - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 1, - 'position' => 5, - 'category_id' => 1, - ); - - // Our event should be executed - $this->assertTrue($action->execute($event)); - - // Our task should have the blue color - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals('blue', $task['color_id']); - } -} diff --git a/sources/tests/units/ActionTaskAssignColorUserTest.php b/sources/tests/units/ActionTaskAssignColorUserTest.php deleted file mode 100644 index ee71e87..0000000 --- a/sources/tests/units/ActionTaskAssignColorUserTest.php +++ /dev/null @@ -1,70 +0,0 @@ -registry)); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskAssignColorUser(1, new Task($this->registry)); - $action->setParam('user_id', 1); - $action->setParam('color_id', 'blue'); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'color_id' => 'green'))); - - // We create an event to move the task to the 2nd column with a user id 5 - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - 'owner_id' => 5, - ); - - // Our event should NOT be executed - $this->assertFalse($action->execute($event)); - - // Our task should be assigned to nobody and have the green color - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(0, $task['owner_id']); - $this->assertEquals('green', $task['color_id']); - - // We create an event to move the task to the 2nd column with a user id 1 - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - 'owner_id' => 1, - ); - - // Our event should be executed - $this->assertTrue($action->execute($event)); - - // Our task should be assigned to nobody and have the blue color - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(0, $task['owner_id']); - $this->assertEquals('blue', $task['color_id']); - } -} diff --git a/sources/tests/units/ActionTaskAssignCurrentUserTest.php b/sources/tests/units/ActionTaskAssignCurrentUserTest.php deleted file mode 100644 index f67c1e2..0000000 --- a/sources/tests/units/ActionTaskAssignCurrentUserTest.php +++ /dev/null @@ -1,73 +0,0 @@ -registry), new Acl($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testBadColumn() - { - $action = new Action\TaskAssignCurrentUser(3, new Task($this->registry), new Acl($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 3, - 'task_id' => 3, - 'column_id' => 3, - ); - - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskAssignCurrentUser(1, new Task($this->registry), new Acl($this->registry)); - $action->setParam('column_id', 2); - $_SESSION = array( - 'user' => array('id' => 5) - ); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $a = new Acl($this->registry); - - $this->assertEquals(5, $a->getUserId()); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should be executed - $this->assertTrue($action->execute($event)); - - // Our task should be assigned to the user 5 (from the session) - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['id']); - $this->assertEquals(5, $task['owner_id']); - } -} diff --git a/sources/tests/units/ActionTaskAssignSpecificUserTest.php b/sources/tests/units/ActionTaskAssignSpecificUserTest.php deleted file mode 100644 index 6fd454d..0000000 --- a/sources/tests/units/ActionTaskAssignSpecificUserTest.php +++ /dev/null @@ -1,66 +0,0 @@ -registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testBadColumn() - { - $action = new Action\TaskAssignSpecificUser(3, new Task($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 3, - 'task_id' => 3, - 'column_id' => 3, - ); - - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskAssignSpecificUser(1, new Task($this->registry)); - $action->setParam('column_id', 2); - $action->setParam('user_id', 1); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should be executed - $this->assertTrue($action->execute($event)); - - // Our task should be assigned to the user 1 - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['owner_id']); - } -} diff --git a/sources/tests/units/ActionTaskCloseTest.php b/sources/tests/units/ActionTaskCloseTest.php deleted file mode 100644 index a3a1eec..0000000 --- a/sources/tests/units/ActionTaskCloseTest.php +++ /dev/null @@ -1,65 +0,0 @@ -registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testBadColumn() - { - $action = new Action\TaskClose(3, new Task($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 3, - 'task_id' => 3, - 'column_id' => 3, - ); - - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskClose(1, new Task($this->registry)); - $action->setParam('column_id', 2); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should be executed - $this->assertTrue($action->execute($event)); - - // Our task should be closed - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(0, $task['is_active']); - } -} diff --git a/sources/tests/units/ActionTaskDuplicateAnotherProjectTest.php b/sources/tests/units/ActionTaskDuplicateAnotherProjectTest.php deleted file mode 100644 index 4d995b6..0000000 --- a/sources/tests/units/ActionTaskDuplicateAnotherProjectTest.php +++ /dev/null @@ -1,89 +0,0 @@ -registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testBadColumn() - { - $action = new Action\TaskDuplicateAnotherProject(3, new Task($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 3, - 'task_id' => 3, - 'column_id' => 3, - ); - - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskDuplicateAnotherProject(1, new Task($this->registry)); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'project 1'))); - $this->assertEquals(2, $p->create(array('name' => 'project 2'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should NOT be executed because we define the same project - $action->setParam('column_id', 2); - $action->setParam('project_id', 1); - $this->assertFalse($action->execute($event)); - - // Our task should be assigned to the project 1 - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['project_id']); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should be executed because we define a different project - $action->setParam('column_id', 2); - $action->setParam('project_id', 2); - $this->assertTrue($action->execute($event)); - - // Our task should be assigned to the project 1 - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['project_id']); - - // We should have another task assigned to the project 2 - $task = $t->getById(2); - $this->assertNotEmpty($task); - $this->assertEquals(2, $task['project_id']); - } -} diff --git a/sources/tests/units/ActionTaskMoveAnotherProjectTest.php b/sources/tests/units/ActionTaskMoveAnotherProjectTest.php deleted file mode 100644 index e6a938d..0000000 --- a/sources/tests/units/ActionTaskMoveAnotherProjectTest.php +++ /dev/null @@ -1,84 +0,0 @@ -registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 2, - 'task_id' => 3, - 'column_id' => 5, - ); - - $this->assertFalse($action->isExecutable($event)); - $this->assertFalse($action->execute($event)); - } - - public function testBadColumn() - { - $action = new Action\TaskMoveAnotherProject(3, new Task($this->registry)); - $action->setParam('column_id', 5); - - $event = array( - 'project_id' => 3, - 'task_id' => 3, - 'column_id' => 3, - ); - - $this->assertFalse($action->execute($event)); - } - - public function testExecute() - { - $action = new Action\TaskMoveAnotherProject(1, new Task($this->registry)); - - // We create a task in the first column - $t = new Task($this->registry); - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'project 1'))); - $this->assertEquals(2, $p->create(array('name' => 'project 2'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should NOT be executed because we define the same project - $action->setParam('column_id', 2); - $action->setParam('project_id', 1); - $this->assertFalse($action->execute($event)); - - // Our task should be assigned to the project 1 - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['project_id']); - - // We create an event to move the task to the 2nd column - $event = array( - 'project_id' => 1, - 'task_id' => 1, - 'column_id' => 2, - ); - - // Our event should be executed because we define a different project - $action->setParam('column_id', 2); - $action->setParam('project_id', 2); - $this->assertTrue($action->execute($event)); - - // Our task should be assigned to the project 2 - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(2, $task['project_id']); - } -} diff --git a/sources/tests/units/ActionTest.php b/sources/tests/units/ActionTest.php deleted file mode 100644 index 23148c4..0000000 --- a/sources/tests/units/ActionTest.php +++ /dev/null @@ -1,256 +0,0 @@ -registry); - $board = new Board($this->registry); - $project = new Project($this->registry); - - $this->assertEquals(1, $project->create(array('name' => 'unit_test'))); - - // We should have nothing - $this->assertEmpty($action->getAll()); - $this->assertEmpty($action->getAllByProject(1)); - - // We create a new action - $this->assertTrue($action->create(array( - 'project_id' => 1, - 'event_name' => Task::EVENT_MOVE_COLUMN, - 'action_name' => 'TaskClose', - 'params' => array( - 'column_id' => 4, - ) - ))); - - // We should have our action - $this->assertNotEmpty($action->getAll()); - $this->assertEquals($action->getAll(), $action->getAllByProject(1)); - - $actions = $action->getAll(); - - $this->assertEquals(1, count($actions)); - $this->assertEquals(1, $actions[0]['project_id']); - $this->assertEquals(Task::EVENT_MOVE_COLUMN, $actions[0]['event_name']); - $this->assertEquals('TaskClose', $actions[0]['action_name']); - $this->assertEquals('column_id', $actions[0]['params'][0]['name']); - $this->assertEquals(4, $actions[0]['params'][0]['value']); - } - - public function testEventMoveColumn() - { - $task = new Task($this->registry); - $board = new Board($this->registry); - $project = new Project($this->registry); - $action = new Action($this->registry); - - // We create a project - $this->assertEquals(1, $project->create(array('name' => 'unit_test'))); - - // We create a task - $this->assertEquals(1, $task->create(array( - 'title' => 'unit_test', - 'project_id' => 1, - 'owner_id' => 1, - 'color_id' => 'red', - 'column_id' => 1, - ))); - - // We create a new action - $this->assertTrue($action->create(array( - 'project_id' => 1, - 'event_name' => Task::EVENT_MOVE_COLUMN, - 'action_name' => 'TaskClose', - 'params' => array( - 'column_id' => 4, - ) - ))); - - // We bind events - $action->attachEvents(); - - // Our task should be open - $t1 = $task->getById(1); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals(1, $t1['column_id']); - - // We move our task - $task->movePosition(1, 1, 4, 1); - - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_COLUMN)); - $this->assertFalse($this->registry->shared('event')->isEventTriggered(Task::EVENT_UPDATE)); - - // Our task should be closed - $t1 = $task->getById(1); - $this->assertEquals(4, $t1['column_id']); - $this->assertEquals(0, $t1['is_active']); - } - - public function testEventMovePosition() - { - $task = new Task($this->registry); - $board = new Board($this->registry); - $project = new Project($this->registry); - $action = new Action($this->registry); - - // We create a project - $this->assertEquals(1, $project->create(array('name' => 'unit_test'))); - - // We create a task - $this->assertEquals(1, $task->create(array( - 'title' => 'unit_test 0', - 'project_id' => 1, - 'owner_id' => 1, - 'color_id' => 'red', - 'column_id' => 1, - 'category_id' => 1, - ))); - - $this->assertEquals(2, $task->create(array( - 'title' => 'unit_test 1', - 'project_id' => 1, - 'owner_id' => 1, - 'color_id' => 'yellow', - 'column_id' => 1, - 'category_id' => 1, - ))); - - // We create a new action, when the category_id=2 then the color_id should be green - $this->assertTrue($action->create(array( - 'project_id' => 1, - 'event_name' => Task::EVENT_MOVE_POSITION, - 'action_name' => 'TaskAssignColorCategory', - 'params' => array( - 'category_id' => 1, - 'color_id' => 'green', - ) - ))); - - // We bind events - $action->attachEvents(); - - $this->assertTrue($this->registry->shared('event')->hasListener(Task::EVENT_MOVE_POSITION, 'Action\TaskAssignColorCategory')); - - // Our task should have the color red and position=1 - $t1 = $task->getById(1); - $this->assertEquals(1, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('red', $t1['color_id']); - - $t1 = $task->getById(2); - $this->assertEquals(2, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('yellow', $t1['color_id']); - - // We move our tasks - $this->assertTrue($task->movePosition(1, 1, 1, 10)); // task #1 to the end of the column - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_POSITION)); - - $t1 = $task->getById(1); - $this->assertEquals(2, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('green', $t1['color_id']); - - $t1 = $task->getById(2); - $this->assertEquals(1, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('yellow', $t1['color_id']); - - $this->registry->shared('event')->clearTriggeredEvents(); - $this->assertTrue($task->movePosition(1, 2, 1, 44)); // task #2 to position 1 - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_POSITION)); - $this->assertEquals('Action\TaskAssignColorCategory', $this->registry->shared('event')->getLastListenerExecuted()); - - $t1 = $task->getById(1); - $this->assertEquals(1, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('green', $t1['color_id']); - - $t1 = $task->getById(2); - $this->assertEquals(2, $t1['position']); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals('green', $t1['color_id']); - } - - public function testExecuteMultipleActions() - { - $task = new Task($this->registry); - $board = new Board($this->registry); - $project = new Project($this->registry); - $action = new Action($this->registry); - - // We create 2 projects - $this->assertEquals(1, $project->create(array('name' => 'unit_test1'))); - $this->assertEquals(2, $project->create(array('name' => 'unit_test2'))); - - // We create a task - $this->assertEquals(1, $task->create(array( - 'title' => 'unit_test', - 'project_id' => 1, - 'owner_id' => 1, - 'color_id' => 'red', - 'column_id' => 1, - ))); - - // We create 2 actions - $this->assertTrue($action->create(array( - 'project_id' => 1, - 'event_name' => Task::EVENT_CLOSE, - 'action_name' => 'TaskDuplicateAnotherProject', - 'params' => array( - 'column_id' => 4, - 'project_id' => 2, - ) - ))); - - $this->assertTrue($action->create(array( - 'project_id' => 1, - 'event_name' => Task::EVENT_MOVE_COLUMN, - 'action_name' => 'TaskClose', - 'params' => array( - 'column_id' => 4, - ) - ))); - - // We bind events - $action->attachEvents(); - - // Events should be attached - $this->assertTrue($this->registry->shared('event')->hasListener(Task::EVENT_CLOSE, 'Action\TaskDuplicateAnotherProject')); - $this->assertTrue($this->registry->shared('event')->hasListener(Task::EVENT_MOVE_COLUMN, 'Action\TaskClose')); - - // Our task should be open, linked to the first project and in the first column - $t1 = $task->getById(1); - $this->assertEquals(1, $t1['is_active']); - $this->assertEquals(1, $t1['column_id']); - $this->assertEquals(1, $t1['project_id']); - - // We move our task - $task->movePosition(1, 1, 4, 1); - - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CLOSE)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_COLUMN)); - - // Our task should be closed - $t1 = $task->getById(1); - $this->assertEquals(4, $t1['column_id']); - $this->assertEquals(0, $t1['is_active']); - - // Our task should be duplicated to the 2nd project - $t2 = $task->getById(2); - $this->assertNotEmpty($t2); - $this->assertNotEquals(4, $t2['column_id']); - $this->assertEquals(1, $t2['is_active']); - $this->assertEquals(2, $t2['project_id']); - $this->assertEquals('unit_test', $t2['title']); - } -} diff --git a/sources/tests/units/Base.php b/sources/tests/units/Base.php deleted file mode 100644 index 3a46a4a..0000000 --- a/sources/tests/units/Base.php +++ /dev/null @@ -1,48 +0,0 @@ -setPath('app'); -$loader->setPath('vendor'); -$loader->execute(); - -abstract class Base extends PHPUnit_Framework_TestCase -{ - public function setUp() - { - $this->registry = new Registry; - $this->registry->db = function() { return setup_db(); }; - $this->registry->event = function() { return setup_events(); }; - - if (DB_DRIVER === 'mysql') { - $pdo = new PDO('mysql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); - $pdo->exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME); - $pdo = null; - } - else if (DB_DRIVER === 'postgres') { - $pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); - $pdo->exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME); - $pdo = null; - } - } - - public function tearDown() - { - $this->registry->shared('db')->closeConnection(); - } -} diff --git a/sources/tests/units/BoardTest.php b/sources/tests/units/BoardTest.php deleted file mode 100644 index 21d6554..0000000 --- a/sources/tests/units/BoardTest.php +++ /dev/null @@ -1,195 +0,0 @@ -registry); - $b = new Board($this->registry); - $c = new Config($this->registry); - - // Default columns - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - $columns = $b->getColumnsList(1); - - $this->assertTrue(is_array($columns)); - $this->assertEquals(4, count($columns)); - $this->assertEquals('Backlog', $columns[1]); - $this->assertEquals('Ready', $columns[2]); - $this->assertEquals('Work in progress', $columns[3]); - $this->assertEquals('Done', $columns[4]); - - // Custom columns: spaces should be trimed and no empty columns - - $this->assertTrue($c->save(array('default_columns' => ' column #1 , column #2, '))); - - $this->assertEquals(2, $p->create(array('name' => 'UnitTest2'))); - $columns = $b->getColumnsList(2); - - $this->assertTrue(is_array($columns)); - $this->assertEquals(2, count($columns)); - $this->assertEquals('column #1', $columns[5]); - $this->assertEquals('column #2', $columns[6]); - } - - public function testGetBoard() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - - $board = $b->get(1); - $this->assertNotEmpty($board); - $this->assertEquals(4, count($board)); - $this->assertTrue(array_key_exists('tasks', $board[2])); - $this->assertTrue(array_key_exists('title', $board[2])); - } - - public function testGetColumn() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - - $column = $b->getColumn(3); - $this->assertNotEmpty($column); - $this->assertEquals('Work in progress', $column['title']); - - $column = $b->getColumn(33); - $this->assertEmpty($column); - } - - public function testRemoveColumn() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - $this->assertTrue($b->removeColumn(3)); - $this->assertFalse($b->removeColumn(322)); - - $columns = $b->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals(3, count($columns)); - } - - public function testUpdateColumn() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - - $this->assertTrue($b->updateColumn(3, 'blah', 5)); - $this->assertTrue($b->updateColumn(2, 'boo')); - - $column = $b->getColumn(3); - $this->assertNotEmpty($column); - $this->assertEquals('blah', $column['title']); - $this->assertEquals(5, $column['task_limit']); - - $column = $b->getColumn(2); - $this->assertNotEmpty($column); - $this->assertEquals('boo', $column['title']); - $this->assertEquals(0, $column['task_limit']); - } - - public function testAddColumn() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - $this->assertTrue($b->addColumn(1, 'another column')); - $this->assertTrue($b->addColumn(1, 'one more', 3)); - - $columns = $b->getColumns(1); - $this->assertTrue(is_array($columns)); - $this->assertEquals(6, count($columns)); - - $this->assertEquals('another column', $columns[4]['title']); - $this->assertEquals(0, $columns[4]['task_limit']); - $this->assertEquals(5, $columns[4]['position']); - - $this->assertEquals('one more', $columns[5]['title']); - $this->assertEquals(3, $columns[5]['task_limit']); - $this->assertEquals(6, $columns[5]['position']); - } - - public function testMoveColumns() - { - $p = new Project($this->registry); - $b = new Board($this->registry); - - // We create 2 projects - $this->assertEquals(1, $p->create(array('name' => 'UnitTest1'))); - $this->assertEquals(2, $p->create(array('name' => 'UnitTest2'))); - - // We get the columns of the project 2 - $columns = $b->getColumns(2); - $columns_id = array_keys($b->getColumnsList(2)); - $this->assertNotEmpty($columns); - - // Initial order: 5, 6, 7, 8 - - // Move the column 1 down - $this->assertEquals(1, $columns[0]['position']); - $this->assertEquals($columns_id[0], $columns[0]['id']); - - $this->assertEquals(2, $columns[1]['position']); - $this->assertEquals($columns_id[1], $columns[1]['id']); - - $this->assertTrue($b->moveDown(2, $columns[0]['id'])); - $columns = $b->getColumns(2); // Sorted by position - - // New order: 6, 5, 7, 8 - - $this->assertEquals(1, $columns[0]['position']); - $this->assertEquals($columns_id[1], $columns[0]['id']); - - $this->assertEquals(2, $columns[1]['position']); - $this->assertEquals($columns_id[0], $columns[1]['id']); - - // Move the column 3 up - $this->assertTrue($b->moveUp(2, $columns[2]['id'])); - $columns = $b->getColumns(2); - - // New order: 6, 7, 5, 8 - - $this->assertEquals(1, $columns[0]['position']); - $this->assertEquals($columns_id[1], $columns[0]['id']); - - $this->assertEquals(2, $columns[1]['position']); - $this->assertEquals($columns_id[2], $columns[1]['id']); - - $this->assertEquals(3, $columns[2]['position']); - $this->assertEquals($columns_id[0], $columns[2]['id']); - - // Move column 1 up (must do nothing because it's the first column) - $this->assertFalse($b->moveUp(2, $columns[0]['id'])); - $columns = $b->getColumns(2); - - // Order: 6, 7, 5, 8 - - $this->assertEquals(1, $columns[0]['position']); - $this->assertEquals($columns_id[1], $columns[0]['id']); - - // Move column 4 down (must do nothing because it's the last column) - $this->assertFalse($b->moveDown(2, $columns[3]['id'])); - $columns = $b->getColumns(2); - - // Order: 6, 7, 5, 8 - - $this->assertEquals(4, $columns[3]['position']); - $this->assertEquals($columns_id[3], $columns[3]['id']); - } -} diff --git a/sources/tests/units/CategoryTest.php b/sources/tests/units/CategoryTest.php deleted file mode 100644 index 201fa58..0000000 --- a/sources/tests/units/CategoryTest.php +++ /dev/null @@ -1,57 +0,0 @@ -registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $c->create(array('name' => 'Category #1', 'project_id' => 1))); - $this->assertEquals(2, $c->create(array('name' => 'Category #2', 'project_id' => 1))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'category_id' => 2))); - - $task = $t->getById(1); - $this->assertTrue(is_array($task)); - $this->assertEquals(2, $task['category_id']); - - $category = $c->getById(2); - $this->assertTrue(is_array($category)); - $this->assertEquals(2, $category['id']); - $this->assertEquals('Category #2', $category['name']); - $this->assertEquals(1, $category['project_id']); - } - - public function testRemove() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $c->create(array('name' => 'Category #1', 'project_id' => 1))); - $this->assertEquals(2, $c->create(array('name' => 'Category #2', 'project_id' => 1))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'category_id' => 2))); - - $task = $t->getById(1); - $this->assertTrue(is_array($task)); - $this->assertEquals(2, $task['category_id']); - - $this->assertTrue($c->remove(1)); - $this->assertTrue($c->remove(2)); - - // Make sure tasks assigned with that category are reseted - $task = $t->getById(1); - $this->assertTrue(is_array($task)); - $this->assertEquals(0, $task['category_id']); - } -} diff --git a/sources/tests/units/CommentTest.php b/sources/tests/units/CommentTest.php deleted file mode 100644 index 31c4699..0000000 --- a/sources/tests/units/CommentTest.php +++ /dev/null @@ -1,133 +0,0 @@ -registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'bla bla', 'user_id' => 1))); - - $comment = $c->getById(1); - - $this->assertNotEmpty($comment); - $this->assertEquals('bla bla', $comment['comment']); - $this->assertEquals(1, $comment['task_id']); - $this->assertEquals(1, $comment['user_id']); - $this->assertEquals('admin', $comment['username']); - $this->assertNotEmpty($comment['date']); - } - - public function testGetAll() - { - $c = new Comment($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c2', 'user_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c3', 'user_id' => 1))); - - $comments = $c->getAll(1); - - $this->assertNotEmpty($comments); - $this->assertEquals(3, count($comments)); - $this->assertEquals(1, $comments[0]['id']); - $this->assertEquals(2, $comments[1]['id']); - $this->assertEquals(3, $comments[2]['id']); - - $this->assertEquals(3, $c->count(1)); - } - - public function testUpdate() - { - $c = new Comment($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - $this->assertTrue($c->update(array('id' => 1, 'comment' => 'bla'))); - - $comment = $c->getById(1); - $this->assertNotEmpty($comment); - $this->assertEquals('bla', $comment['comment']); - } - - public function validateRemove() - { - $c = new Comment($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); - $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); - - $this->assertTrue($c->remove(1)); - $this->assertFalse($c->remove(1)); - $this->assertFalse($c->remove(1111)); - } - - public function testValidateCreation() - { - $c = new Comment($this->registry); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => 'bla')); - $this->assertTrue($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => '')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 'a', 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 'b', 'task_id' => 1, 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('task_id' => 1, 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array()); - $this->assertFalse($result[0]); - } - - public function testValidateModification() - { - $c = new Comment($this->registry); - - $result = $c->validateModification(array('id' => 1, 'comment' => 'bla')); - $this->assertTrue($result[0]); - - $result = $c->validateModification(array('id' => 1, 'comment' => '')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array('comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array('id' => 'b', 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array()); - $this->assertFalse($result[0]); - } -} diff --git a/sources/tests/units/ConfigTest.php b/sources/tests/units/ConfigTest.php deleted file mode 100644 index 7298936..0000000 --- a/sources/tests/units/ConfigTest.php +++ /dev/null @@ -1,32 +0,0 @@ -registry); - - $this->assertEquals('en_US', $c->get('language')); - $this->assertEquals('UTC', $c->get('timezone')); - - $this->assertEmpty($c->get('webhooks_url_task_modification')); - $this->assertEmpty($c->get('webhooks_url_task_creation')); - $this->assertEmpty($c->get('default_columns')); - - $this->assertNotEmpty($c->get('webhooks_token')); - $this->assertNotEmpty($c->get('api_token')); - } - - public function testGet() - { - $c = new Config($this->registry); - - $this->assertEquals('', $c->get('default_columns')); - $this->assertEquals('test', $c->get('default_columns', 'test')); - $this->assertEquals(0, $c->get('default_columns', 0)); - } -} diff --git a/sources/tests/units/ProjectTest.php b/sources/tests/units/ProjectTest.php deleted file mode 100644 index dc71d5a..0000000 --- a/sources/tests/units/ProjectTest.php +++ /dev/null @@ -1,281 +0,0 @@ -registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals(1, $project['is_active']); - $this->assertEquals(0, $project['is_public']); - $this->assertEquals(time(), $project['last_modified']); - $this->assertEmpty($project['token']); - } - - public function testUpdateLastModifiedDate() - { - $p = new Project($this->registry); - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - $now = time(); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals($now, $project['last_modified']); - - sleep(1); - $this->assertTrue($p->updateModificationDate(1)); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals($now + 1, $project['last_modified']); - } - - public function testIsLastModified() - { - $p = new Project($this->registry); - $t = new Task($this->registry); - - $now = time(); - $p->attachEvents(); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals($now, $project['last_modified']); - - sleep(1); - - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE)); - $this->assertEquals('Event\ProjectModificationDate', $this->registry->shared('event')->getLastListenerExecuted()); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertTrue($p->isModifiedSince(1, $now)); - } - - public function testRemove() - { - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertTrue($p->remove(1)); - $this->assertFalse($p->remove(1234)); - } - - public function testEnable() - { - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertTrue($p->disable(1)); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals(0, $project['is_active']); - - $this->assertFalse($p->disable(1111)); - } - - public function testDisable() - { - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertTrue($p->disable(1)); - $this->assertTrue($p->enable(1)); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals(1, $project['is_active']); - - $this->assertFalse($p->enable(1234567)); - } - - public function testEnablePublicAccess() - { - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertTrue($p->enablePublicAccess(1)); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals(1, $project['is_public']); - $this->assertNotEmpty($project['token']); - - $this->assertFalse($p->enablePublicAccess(123)); - } - - public function testDisablePublicAccess() - { - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertTrue($p->enablePublicAccess(1)); - $this->assertTrue($p->disablePublicAccess(1)); - - $project = $p->getById(1); - $this->assertNotEmpty($project); - $this->assertEquals(0, $project['is_public']); - $this->assertEmpty($project['token']); - - $this->assertFalse($p->disablePublicAccess(123)); - } - - public function testAllowEverybody() - { - // We create a regular user - $user = new User($this->registry); - $user->create(array('username' => 'unittest', 'password' => 'unittest')); - - $p = new Project($this->registry); - $this->assertEmpty($p->getAllowedUsers(1)); // Nobody is specified for the given project - $this->assertTrue($p->isUserAllowed(1, 1)); // Everybody should be allowed - $this->assertTrue($p->isUserAllowed(1, 2)); // Everybody should be allowed - } - - public function testAllowUser() - { - $p = new Project($this->registry); - $user = new User($this->registry); - $user->create(array('username' => 'unittest', 'password' => 'unittest')); - - // We create a project - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - // We allow the admin user - $this->assertTrue($p->allowUser(1, 1)); - - // Non-existant project - $this->assertFalse($p->allowUser(50, 1)); - - // Non-existant user - $this->assertFalse($p->allowUser(1, 50)); - - // Our admin user should be allowed - $this->assertEquals(array('1' => 'admin'), $p->getAllowedUsers(1)); - $this->assertTrue($p->isUserAllowed(1, 1)); - - // Our regular user should be forbidden - $this->assertFalse($p->isUserAllowed(1, 2)); - } - - public function testRevokeUser() - { - $p = new Project($this->registry); - - $user = new User($this->registry); - $user->create(array('username' => 'unittest', 'password' => 'unittest')); - - // We create a project - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - // We revoke our admin user (not existing row) - $this->assertFalse($p->revokeUser(1, 1)); - - // We should have nobody in the users list - $this->assertEmpty($p->getAllowedUsers(1)); - - // Our admin user and our regular user should be allowed - $this->assertTrue($p->isUserAllowed(1, 1)); - $this->assertTrue($p->isUserAllowed(1, 2)); - - // We allow only the regular user - $this->assertTrue($p->allowUser(1, 2)); - - // All users should be allowed (admin and regular) - $this->assertTrue($p->isUserAllowed(1, 1)); - $this->assertTrue($p->isUserAllowed(1, 2)); - - // However, we should have only our regular user in the list - $this->assertEquals(array('2' => 'unittest'), $p->getAllowedUsers(1)); - - // We allow our admin, we should have both in the list - $this->assertTrue($p->allowUser(1, 1)); - $this->assertEquals(array('1' => 'admin', '2' => 'unittest'), $p->getAllowedUsers(1)); - $this->assertTrue($p->isUserAllowed(1, 1)); - $this->assertTrue($p->isUserAllowed(1, 2)); - - // We revoke the regular user - $this->assertTrue($p->revokeUser(1, 2)); - - // Only admin should be allowed - $this->assertTrue($p->isUserAllowed(1, 1)); - $this->assertFalse($p->isUserAllowed(1, 2)); - - // We should have only admin in the list - $this->assertEquals(array('1' => 'admin'), $p->getAllowedUsers(1)); - - // We revoke the admin user - $this->assertTrue($p->revokeUser(1, 1)); - $this->assertEmpty($p->getAllowedUsers(1)); - - // Everybody should be allowed again - $this->assertTrue($p->isUserAllowed(1, 1)); - $this->assertTrue($p->isUserAllowed(1, 2)); - } - - public function testUsersList() - { - $p = new Project($this->registry); - - $user = new User($this->registry); - $user->create(array('username' => 'unittest', 'password' => 'unittest')); - - // We create project - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - - // No restriction, we should have everybody - $this->assertEquals( - array('Unassigned', 'admin', 'unittest'), - $p->getUsersList(1) - ); - - // We allow only the regular user - $this->assertTrue($p->allowUser(1, 2)); - - $this->assertEquals( - array(0 => 'Unassigned', 2 => 'unittest'), - $p->getUsersList(1) - ); - - // We allow the admin user - $this->assertTrue($p->allowUser(1, 1)); - - $this->assertEquals( - array(0 => 'Unassigned', 1 => 'admin', 2 => 'unittest'), - $p->getUsersList(1) - ); - - // We revoke only the regular user - $this->assertTrue($p->revokeUser(1, 2)); - - $this->assertEquals( - array(0 => 'Unassigned', 1 => 'admin'), - $p->getUsersList(1) - ); - - // We revoke only the admin user, we should have everybody - $this->assertTrue($p->revokeUser(1, 1)); - - $this->assertEquals( - array(0 => 'Unassigned', 1 => 'admin', 2 => 'unittest'), - $p->getUsersList(1) - ); - } -} diff --git a/sources/tests/units/SubtaskTest.php b/sources/tests/units/SubtaskTest.php deleted file mode 100644 index a74ee60..0000000 --- a/sources/tests/units/SubtaskTest.php +++ /dev/null @@ -1,56 +0,0 @@ -registry); - $s = new SubTask($this->registry); - $p = new Project($this->registry); - - // We create a project - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - - // We create 2 tasks - $this->assertEquals(1, $t->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1))); - $this->assertEquals(2, $t->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 0))); - - // We create many subtasks for the first task - $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_estimated' => 5, 'time_spent' => 3, 'status' => 1))); - $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => 0, 'time_spent' => 0, 'status' => 2, 'user_id' => 1))); - - // We duplicate our subtasks - $this->assertTrue($s->duplicate(1, 2)); - $subtasks = $s->getAll(2); - - $this->assertNotFalse($subtasks); - $this->assertNotEmpty($subtasks); - $this->assertEquals(2, count($subtasks)); - - $this->assertEquals('subtask #1', $subtasks[0]['title']); - $this->assertEquals('subtask #2', $subtasks[1]['title']); - - $this->assertEquals(2, $subtasks[0]['task_id']); - $this->assertEquals(2, $subtasks[1]['task_id']); - - $this->assertEquals(5, $subtasks[0]['time_estimated']); - $this->assertEquals(0, $subtasks[1]['time_estimated']); - - $this->assertEquals(0, $subtasks[0]['time_spent']); - $this->assertEquals(0, $subtasks[1]['time_spent']); - - $this->assertEquals(0, $subtasks[0]['status']); - $this->assertEquals(0, $subtasks[1]['status']); - - $this->assertEquals(0, $subtasks[0]['user_id']); - $this->assertEquals(0, $subtasks[1]['user_id']); - } -} diff --git a/sources/tests/units/TaskHistoryTest.php b/sources/tests/units/TaskHistoryTest.php deleted file mode 100644 index 085162e..0000000 --- a/sources/tests/units/TaskHistoryTest.php +++ /dev/null @@ -1,98 +0,0 @@ -registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1))); - - $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); - $this->assertTrue($e->create(1, 2, 1, Task::EVENT_UPDATE)); - $this->assertFalse($e->create(1, 1, 0, Task::EVENT_OPEN)); - - $events = $e->getAllByProjectId(1); - - $this->assertNotEmpty($events); - $this->assertTrue(is_array($events)); - $this->assertEquals(2, count($events)); - $this->assertEquals(time(), $events[0]['date_creation']); - $this->assertEquals(Task::EVENT_UPDATE, $events[0]['event_name']); - $this->assertEquals(Task::EVENT_CLOSE, $events[1]['event_name']); - } - - public function testFetchAllContent() - { - $e = new TaskHistory($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); - - $nb_events = 80; - - for ($i = 0; $i < $nb_events; $i++) { - $this->assertTrue($e->create(1, 1, 1, Task::EVENT_UPDATE)); - } - - $events = $e->getAllContentByProjectId(1); - - $this->assertNotEmpty($events); - $this->assertTrue(is_array($events)); - $this->assertEquals(50, count($events)); - $this->assertEquals('admin', $events[0]['author']); - $this->assertNotEmpty($events[0]['event_title']); - $this->assertNotEmpty($events[0]['event_content']); - } - - public function testCleanup() - { - $e = new TaskHistory($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); - - $max = 15; - $nb_events = 100; - - for ($i = 0; $i < $nb_events; $i++) { - $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); - } - - $this->assertEquals($nb_events, $this->registry->shared('db')->table('task_has_events')->count()); - $e->cleanup($max); - - $events = $e->getAllByProjectId(1); - - $this->assertNotEmpty($events); - $this->assertTrue(is_array($events)); - $this->assertEquals($max, count($events)); - $this->assertEquals(100, $events[0]['id']); - $this->assertEquals(99, $events[1]['id']); - $this->assertEquals(86, $events[14]['id']); - - // Cleanup during task creation - - $nb_events = TaskHistory::MAX_EVENTS + 10; - - for ($i = 0; $i < $nb_events; $i++) { - $this->assertTrue($e->create(1, 1, 1, Task::EVENT_CLOSE)); - } - - $this->assertEquals(TaskHistory::MAX_EVENTS, $this->registry->shared('db')->table('task_has_events')->count()); - } -} diff --git a/sources/tests/units/TaskTest.php b/sources/tests/units/TaskTest.php deleted file mode 100644 index ad9f4cb..0000000 --- a/sources/tests/units/TaskTest.php +++ /dev/null @@ -1,676 +0,0 @@ -registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); - - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - $this->assertEquals('yellow', $task['color_id']); - $this->assertEquals(time(), $task['date_creation']); - $this->assertEquals(time(), $task['date_modification']); - $this->assertEquals(0, $task['date_due']); - - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1))); - - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(2, $task['position']); - $this->assertEquals(time(), $task['date_creation']); - $this->assertEquals(time(), $task['date_modification']); - $this->assertEquals(0, $task['date_due']); - - $tasks = $t->getAll(1, 1); - $this->assertNotEmpty($tasks); - $this->assertTrue(is_array($tasks)); - $this->assertEquals(1, $tasks[0]['id']); - $this->assertEquals(2, $tasks[1]['id']); - - $tasks = $t->getAll(1, 0); - $this->assertEmpty($tasks); - } - - public function testRemove() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1))); - - $this->assertTrue($t->remove(1)); - $this->assertFalse($t->remove(1234)); - } - - public function testGetOverdueTasks() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'date_due' => strtotime('-1 day')))); - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'date_due' => strtotime('+1 day')))); - $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'date_due' => 0))); - $this->assertEquals(4, $t->create(array('title' => 'Task #3', 'project_id' => 1))); - - $tasks = $t->getOverdueTasks(); - $this->assertNotEmpty($tasks); - $this->assertTrue(is_array($tasks)); - $this->assertEquals(1, count($tasks)); - $this->assertEquals('Task #1', $tasks[0]['title']); - } - - public function testMoveTaskWithColumnThatNotChange() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(4, $t->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 2))); - $this->assertEquals(5, $t->create(array('title' => 'Task #5', 'project_id' => 1, 'column_id' => 2))); - $this->assertEquals(6, $t->create(array('title' => 'Task #6', 'project_id' => 1, 'column_id' => 2))); - $this->assertEquals(7, $t->create(array('title' => 'Task #7', 'project_id' => 1, 'column_id' => 3))); - $this->assertEquals(8, $t->create(array('title' => 'Task #8', 'project_id' => 1, 'column_id' => 1))); - - // We move the task 3 to the column 3 - $this->assertTrue($t->movePosition(1, 3, 3, 2)); - - // Check tasks position - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(3); - $this->assertEquals(3, $task['id']); - $this->assertEquals(3, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(5); - $this->assertEquals(5, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(6); - $this->assertEquals(6, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - - $task = $t->getById(7); - $this->assertEquals(7, $task['id']); - $this->assertEquals(3, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(8); - $this->assertEquals(8, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(3, $task['position']); - } - - public function testMoveTaskWithBadPreviousPosition() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $this->registry->shared('db')->table('tasks')->insert(array('title' => 'A', 'column_id' => 1, 'project_id' => 1, 'position' => 1))); - - // Both tasks have the same position - $this->assertEquals(2, $this->registry->shared('db')->table('tasks')->insert(array('title' => 'B', 'column_id' => 2, 'project_id' => 1, 'position' => 1))); - $this->assertEquals(3, $this->registry->shared('db')->table('tasks')->insert(array('title' => 'C', 'column_id' => 2, 'project_id' => 1, 'position' => 1))); - - // Move the first column to the last position of the 2nd column - $this->assertTrue($t->movePosition(1, 1, 2, 3)); - - // Check tasks position - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(3); - $this->assertEquals(3, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - } - - public function testMoveTaskTop() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(4, $t->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); - - // Move the last task to the top - $this->assertTrue($t->movePosition(1, 4, 1, 1)); - - // Check tasks position - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(3, $task['position']); - - $task = $t->getById(3); - $this->assertEquals(3, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(4, $task['position']); - - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - } - - public function testMoveTaskBottom() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(2, $t->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(3, $t->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1))); - $this->assertEquals(4, $t->create(array('title' => 'Task #4', 'project_id' => 1, 'column_id' => 1))); - - // Move the last task to hte top - $this->assertTrue($t->movePosition(1, 1, 1, 4)); - - // Check tasks position - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(4, $task['position']); - - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(3); - $this->assertEquals(3, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(3, $task['position']); - } - - public function testMovePosition() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $counter = 1; - $task_per_column = 5; - - foreach (array(1, 2, 3, 4) as $column_id) { - - for ($i = 1; $i <= $task_per_column; $i++, $counter++) { - - $task = array( - 'title' => 'Task #'.$i.'-'.$column_id, - 'project_id' => 1, - 'column_id' => $column_id, - 'owner_id' => 0, - ); - - $this->assertEquals($counter, $t->create($task)); - - $task = $t->getById($counter); - $this->assertNotFalse($task); - $this->assertNotEmpty($task); - $this->assertEquals($i, $task['position']); - } - } - - // We move task id #4, column 1, position 4 to the column 2, position 3 - $this->assertTrue($t->movePosition(1, 4, 2, 3)); - - // We check the new position of the task - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - - // The tasks before have the correct position - $task = $t->getById(3); - $this->assertEquals(3, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(3, $task['position']); - - $task = $t->getById(7); - $this->assertEquals(7, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(2, $task['position']); - - // The tasks after have the correct position - $task = $t->getById(5); - $this->assertEquals(5, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(4, $task['position']); - - $task = $t->getById(8); - $this->assertEquals(8, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(4, $task['position']); - - // The number of tasks per column - $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 1)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); - $this->assertEquals($task_per_column, $t->countByColumnId(1, 3)); - $this->assertEquals($task_per_column, $t->countByColumnId(1, 4)); - - // We move task id #1, column 1, position 1 to the column 4, position 6 (last position) - $this->assertTrue($t->movePosition(1, 1, 4, $task_per_column + 1)); - - // We check the new position of the task - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(4, $task['column_id']); - $this->assertEquals($task_per_column + 1, $task['position']); - - // The tasks before have the correct position - $task = $t->getById(20); - $this->assertEquals(20, $task['id']); - $this->assertEquals(4, $task['column_id']); - $this->assertEquals($task_per_column, $task['position']); - - // The tasks after have the correct position - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - - // The number of tasks per column - $this->assertEquals($task_per_column - 2, $t->countByColumnId(1, 1)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); - $this->assertEquals($task_per_column, $t->countByColumnId(1, 3)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); - - // Our previous moved task should stay at the same place - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - - // Test wrong position number - $this->assertFalse($t->movePosition(1, 2, 3, 0)); - $this->assertFalse($t->movePosition(1, 2, 3, -2)); - - // Wrong column - $this->assertFalse($t->movePosition(1, 2, 22, 2)); - - // Test position greater than the last position - $this->assertTrue($t->movePosition(1, 11, 1, 22)); - - $task = $t->getById(11); - $this->assertEquals(11, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals($t->countByColumnId(1, 1), $task['position']); - - $task = $t->getById(5); - $this->assertEquals(5, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals($t->countByColumnId(1, 1) - 1, $task['position']); - - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - - $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 1)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); - $this->assertEquals($task_per_column - 1, $t->countByColumnId(1, 3)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); - - // Our previous moved task should stay at the same place - $task = $t->getById(4); - $this->assertEquals(4, $task['id']); - $this->assertEquals(2, $task['column_id']); - $this->assertEquals(3, $task['position']); - - // Test moving task to position 1 - $this->assertTrue($t->movePosition(1, 14, 1, 1)); - - $task = $t->getById(14); - $this->assertEquals(14, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(1, $task['position']); - - $task = $t->getById(2); - $this->assertEquals(2, $task['id']); - $this->assertEquals(1, $task['column_id']); - $this->assertEquals(2, $task['position']); - - $this->assertEquals($task_per_column, $t->countByColumnId(1, 1)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 2)); - $this->assertEquals($task_per_column - 2, $t->countByColumnId(1, 3)); - $this->assertEquals($task_per_column + 1, $t->countByColumnId(1, 4)); - } - - public function testExport() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'Export Project'))); - $this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1))); - $this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 1))); - $this->assertNotFalse($c->create(array('name' => 'Category #3', 'project_id' => 1))); - - for ($i = 1; $i <= 100; $i++) { - - $task = array( - 'title' => 'Task #'.$i, - 'project_id' => 1, - 'column_id' => rand(1, 3), - 'creator_id' => rand(0, 1), - 'owner_id' => rand(0, 1), - 'color_id' => rand(0, 1) === 0 ? 'green' : 'purple', - 'category_id' => rand(0, 3), - 'date_due' => array_rand(array(0, date('Y-m-d'), date('Y-m-d', strtotime('+'.$i.'day')))), - 'score' => rand(0, 21) - ); - - $this->assertEquals($i, $t->create($task)); - } - - $rows = $t->export(1, strtotime('-1 day'), strtotime('+1 day')); - $this->assertEquals($i, count($rows)); - $this->assertEquals('Task Id', $rows[0][0]); - $this->assertEquals(1, $rows[1][0]); - $this->assertEquals('Task #'.($i - 1), $rows[$i - 1][11]); - } - - public function testFilter() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(1, $t->create(array('title' => 'test a', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1, 'description' => 'biloute'))); - $this->assertEquals(2, $t->create(array('title' => 'test b', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 2, 'description' => 'toto et titi sont dans un bateau'))); - - $tasks = $t->find(array(array('column' => 'project_id', 'operator' => 'eq', 'value' => '1'))); - $this->assertNotFalse($tasks); - $this->assertEquals(2, count($tasks)); - $this->assertEquals(1, $tasks[0]['id']); - $this->assertEquals(2, $tasks[1]['id']); - - $tasks = $t->find(array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => '1'), - array('column' => 'owner_id', 'operator' => 'eq', 'value' => '2'), - )); - $this->assertEquals(1, count($tasks)); - $this->assertEquals(2, $tasks[0]['id']); - - $tasks = $t->find(array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => '1'), - array('column' => 'title', 'operator' => 'like', 'value' => '%b%'), - )); - $this->assertEquals(1, count($tasks)); - $this->assertEquals(2, $tasks[0]['id']); - - // Condition with OR - $search = 'bateau'; - $filters = array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => 1), - 'or' => array( - array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), - array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), - ) - ); - - $tasks = $t->find($filters); - $this->assertEquals(1, count($tasks)); - $this->assertEquals(2, $tasks[0]['id']); - - $search = 'toto et titi'; - $filters = array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => 1), - 'or' => array( - array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), - array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), - ) - ); - - $tasks = $t->find($filters); - $this->assertEquals(1, count($tasks)); - $this->assertEquals(2, $tasks[0]['id']); - - $search = 'john'; - $filters = array( - array('column' => 'project_id', 'operator' => 'eq', 'value' => 1), - 'or' => array( - array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), - array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), - ) - ); - - $tasks = $t->find($filters); - $this->assertEquals(0, count($tasks)); - } - - public function testDateFormat() - { - $t = new Task($this->registry); - - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('2014-03-05', 'Y-m-d'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('2014_03_05', 'Y_m_d'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('05/03/2014', 'd/m/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('03/05/2014', 'm/d/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('3/5/2014', 'm/d/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('5/3/2014', 'd/m/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->getValidDate('5/3/14', 'd/m/y'))); - $this->assertEquals(0, $t->getValidDate('5/3/14', 'd/m/Y')); - $this->assertEquals(0, $t->getValidDate('5-3-2014', 'd/m/Y')); - - $this->assertEquals('2014-03-05', date('Y-m-d', $t->parseDate('2014-03-05'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->parseDate('2014_03_05'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $t->parseDate('03/05/2014'))); - } - - public function testDuplicateToTheSameProject() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - // We create a task and a project - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - - // Some categories - $this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1))); - $this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 1))); - $this->assertTrue($c->exists(1, 1)); - $this->assertTrue($c->exists(2, 1)); - - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1, 'category_id' => 2))); - - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['position']); - - // We duplicate our task - $this->assertEquals(2, $t->duplicateSameProject($task)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE)); - - // Check the values of the duplicated task - $task = $t->getById(2); - $this->assertNotEmpty($task); - $this->assertEquals(Task::STATUS_OPEN, $task['is_active']); - $this->assertEquals(1, $task['project_id']); - $this->assertEquals(1, $task['owner_id']); - $this->assertEquals(2, $task['category_id']); - $this->assertEquals(3, $task['column_id']); - $this->assertEquals(2, $task['position']); - } - - public function testDuplicateToAnotherProject() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - $c = new Category($this->registry); - - // We create 2 projects - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(2, $p->create(array('name' => 'test2'))); - - $this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1))); - $this->assertTrue($c->exists(1, 1)); - - // We create a task - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'owner_id' => 1, 'category_id' => 1))); - $task = $t->getById(1); - - // We duplicate our task to the 2nd project - $this->assertEquals(2, $t->duplicateToAnotherProject(2, $task)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE)); - - // Check the values of the duplicated task - $task = $t->getById(2); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['owner_id']); - $this->assertEquals(0, $task['category_id']); - $this->assertEquals(5, $task['column_id']); - $this->assertEquals(1, $task['position']); - $this->assertEquals(2, $task['project_id']); - $this->assertEquals('test', $task['title']); - } - - public function testMoveToAnotherProject() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - $user = new User($this->registry); - - // We create a regular user - $user->create(array('username' => 'unittest1', 'password' => 'unittest')); - $user->create(array('username' => 'unittest2', 'password' => 'unittest')); - - // We create 2 projects - $this->assertEquals(1, $p->create(array('name' => 'test1'))); - $this->assertEquals(2, $p->create(array('name' => 'test2'))); - - // We create a task - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1, 'category_id' => 10, 'position' => 333))); - $this->assertEquals(2, $t->create(array('title' => 'test2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 3, 'category_id' => 10, 'position' => 333))); - - // We duplicate our task to the 2nd project - $task = $t->getById(1); - $this->assertEquals(1, $t->moveToAnotherProject(2, $task)); - //$this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE)); - - // Check the values of the duplicated task - $task = $t->getById(1); - $this->assertNotEmpty($task); - $this->assertEquals(1, $task['owner_id']); - $this->assertEquals(0, $task['category_id']); - $this->assertEquals(2, $task['project_id']); - $this->assertEquals(5, $task['column_id']); - $this->assertEquals(1, $task['position']); - $this->assertEquals('test', $task['title']); - - // We allow only one user on the second project - $this->assertTrue($p->allowUser(2, 2)); - - // The owner should be reseted - $task = $t->getById(2); - $this->assertEquals(2, $t->moveToAnotherProject(2, $task)); - - $task = $t->getById(2); - $this->assertNotEmpty($task); - $this->assertEquals(0, $task['owner_id']); - } - - public function testEvents() - { - $t = new Task($this->registry); - $p = new Project($this->registry); - - // We create a project - $this->assertEquals(1, $p->create(array('name' => 'test'))); - - // We create task - $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1))); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE)); - - // We update a task - $this->assertTrue($t->update(array('title' => 'test2', 'id' => 1))); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_UPDATE)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CREATE_UPDATE)); - - // We close our task - $this->assertTrue($t->close(1)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_CLOSE)); - - // We open our task - $this->assertTrue($t->open(1)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_OPEN)); - - // We change the column of our task - $this->assertTrue($t->movePosition(1, 1, 2, 1)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_COLUMN)); - - // We change the position of our task - $this->assertEquals(2, $t->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 2))); - $this->assertTrue($t->movePosition(1, 1, 2, 2)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_POSITION)); - - // We change the column and the position of our task - $this->assertTrue($t->movePosition(1, 1, 1, 1)); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_MOVE_COLUMN)); - - // We change the assignee - $this->assertTrue($t->update(array('owner_id' => 1, 'id' => 1))); - $this->assertTrue($this->registry->shared('event')->isEventTriggered(Task::EVENT_ASSIGNEE_CHANGE)); - } -} diff --git a/sources/tests/units/UserTest.php b/sources/tests/units/UserTest.php deleted file mode 100644 index cc0b7c4..0000000 --- a/sources/tests/units/UserTest.php +++ /dev/null @@ -1,82 +0,0 @@ -registry); - $this->assertTrue($u->create(array('username' => 'toto', 'password' => '123456', 'name' => 'Toto'))); - $this->assertTrue($u->create(array('username' => 'titi', 'is_ldap_user' => 1))); - $this->assertFalse($u->create(array('username' => 'toto'))); - - $user = $u->getById(1); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('admin', $user['username']); - $this->assertEquals('', $user['name']); - $this->assertEquals(1, $user['is_admin']); - $this->assertEquals(0, $user['is_ldap_user']); - - $user = $u->getById(2); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('toto', $user['username']); - $this->assertEquals('Toto', $user['name']); - $this->assertEquals(0, $user['is_admin']); - $this->assertEquals(0, $user['is_ldap_user']); - - $user = $u->getById(3); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('titi', $user['username']); - $this->assertEquals('', $user['name']); - $this->assertEquals(0, $user['is_admin']); - $this->assertEquals(1, $user['is_ldap_user']); - } - - public function testUpdate() - { - $u = new User($this->registry); - $this->assertTrue($u->create(array('username' => 'toto', 'password' => '123456', 'name' => 'Toto'))); - $this->assertTrue($u->update(array('id' => 2, 'username' => 'biloute'))); - - $user = $u->getById(2); - $this->assertNotFalse($user); - $this->assertTrue(is_array($user)); - $this->assertEquals('biloute', $user['username']); - $this->assertEquals('Toto', $user['name']); - $this->assertEquals(0, $user['is_admin']); - $this->assertEquals(0, $user['is_ldap_user']); - } - - public function testRemove() - { - $u = new User($this->registry); - $t = new Task($this->registry); - $p = new Project($this->registry); - - $this->assertTrue($u->create(array('username' => 'toto', 'password' => '123456', 'name' => 'Toto'))); - $this->assertEquals(1, $p->create(array('name' => 'Project #1'))); - $this->assertEquals(1, $t->create(array('title' => 'Task #1', 'project_id' => 1, 'owner_id' => 2))); - - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(2, $task['owner_id']); - - $this->assertTrue($u->remove(1)); - $this->assertTrue($u->remove(2)); - $this->assertFalse($u->remove(2)); - $this->assertFalse($u->remove(55)); - - // Make sure that assigned tasks are unassigned after removing the user - $task = $t->getById(1); - $this->assertEquals(1, $task['id']); - $this->assertEquals(0, $task['owner_id']); - } -} diff --git a/sources/vendor/JsonRPC/Client.php b/sources/vendor/JsonRPC/Client.php index 65aed22..d9c785e 100644 --- a/sources/vendor/JsonRPC/Client.php +++ b/sources/vendor/JsonRPC/Client.php @@ -88,8 +88,13 @@ class Client * @param array $params Procedure arguments * @return mixed */ - public function __call($method, $params) + public function __call($method, array $params) { + // Allow to pass an array and use named arguments + if (count($params) === 1 && is_array($params[0])) { + $params = $params[0]; + } + return $this->execute($method, $params); } diff --git a/sources/vendor/Michelf/Markdown.inc.php b/sources/vendor/Michelf/Markdown.inc.php deleted file mode 100644 index 8c28109..0000000 --- a/sources/vendor/Michelf/Markdown.inc.php +++ /dev/null @@ -1,10 +0,0 @@ - -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# -namespace Michelf; - - -# -# Markdown Parser Class -# - -class Markdown implements MarkdownInterface { - - ### Version ### - - const MARKDOWNLIB_VERSION = "1.4.1"; - - ### Simple Function Interface ### - - public static function defaultTransform($text) { - # - # Initialize the parser and return the result of its transform method. - # This will work fine for derived classes too. - # - # Take parser class on which this function was called. - $parser_class = \get_called_class(); - - # try to take parser from the static parser list - static $parser_list; - $parser =& $parser_list[$parser_class]; - - # create the parser it not already set - if (!$parser) - $parser = new $parser_class; - - # Transform text using parser. - return $parser->transform($text); - } - - ### Configuration Variables ### - - # Change to ">" for HTML output. - public $empty_element_suffix = " />"; - public $tab_width = 4; - - # Change to `true` to disallow markup or entities. - public $no_markup = false; - public $no_entities = false; - - # Predefined urls and titles for reference links and images. - public $predef_urls = array(); - public $predef_titles = array(); - - # Optional filter function for URLs - public $url_filter_func = null; - - - ### Parser Implementation ### - - # Regex to match balanced [brackets]. - # Needed to insert a maximum bracked depth while converting to PHP. - protected $nested_brackets_depth = 6; - protected $nested_brackets_re; - - protected $nested_url_parenthesis_depth = 4; - protected $nested_url_parenthesis_re; - - # Table of hash values for escaped characters: - protected $escape_chars = '\`*_{}[]()>#+-.!'; - protected $escape_chars_re; - - - public function __construct() { - # - # Constructor function. Initialize appropriate member variables. - # - $this->_initDetab(); - $this->prepareItalicsAndBold(); - - $this->nested_brackets_re = - str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). - str_repeat('\])*', $this->nested_brackets_depth); - - $this->nested_url_parenthesis_re = - str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). - str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); - - $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; - - # Sort document, block, and span gamut in ascendent priority order. - asort($this->document_gamut); - asort($this->block_gamut); - asort($this->span_gamut); - } - - - # Internal hashes used during transformation. - protected $urls = array(); - protected $titles = array(); - protected $html_hashes = array(); - - # Status flag to avoid invalid nesting. - protected $in_anchor = false; - - - protected function setup() { - # - # Called before the transformation process starts to setup parser - # states. - # - # Clear global hashes. - $this->urls = $this->predef_urls; - $this->titles = $this->predef_titles; - $this->html_hashes = array(); - - $this->in_anchor = false; - } - - protected function teardown() { - # - # Called after the transformation process to clear any variable - # which may be taking up memory unnecessarly. - # - $this->urls = array(); - $this->titles = array(); - $this->html_hashes = array(); - } - - - public function transform($text) { - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # - $this->setup(); - - # Remove UTF-8 BOM and marker character in input, if present. - $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); - - # Standardize line endings: - # DOS to Unix and Mac to Unix - $text = preg_replace('{\r\n?}', "\n", $text); - - # Make sure $text ends with a couple of newlines: - $text .= "\n\n"; - - # Convert all tabs to spaces. - $text = $this->detab($text); - - # Turn block-level HTML blocks into hash entries - $text = $this->hashHTMLBlocks($text); - - # Strip any lines consisting only of spaces and tabs. - # This makes subsequent regexen easier to write, because we can - # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ ]*\n+/ . - $text = preg_replace('/^[ ]+$/m', '', $text); - - # Run document gamut methods. - foreach ($this->document_gamut as $method => $priority) { - $text = $this->$method($text); - } - - $this->teardown(); - - return $text . "\n"; - } - - protected $document_gamut = array( - # Strip link definitions, store in hashes. - "stripLinkDefinitions" => 20, - - "runBasicBlockGamut" => 30, - ); - - - protected function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - (?: - <(.+?)> # url = $2 - | - (\S+?) # url = $3 - ) - [ ]* - \n? # maybe one newline - [ ]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.*?) # title = $4 - [")] - [ ]* - )? # title is optional - (?:\n+|\Z) - }xm', - array($this, '_stripLinkDefinitions_callback'), - $text); - return $text; - } - protected function _stripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $url = $matches[2] == '' ? $matches[3] : $matches[2]; - $this->urls[$link_id] = $url; - $this->titles[$link_id] =& $matches[4]; - return ''; # String that will replace the block - } - - - protected function hashHTMLBlocks($text) { - if ($this->no_markup) return $text; - - $less_than_tab = $this->tab_width - 1; - - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded: - # - # * List "a" is made of tags which can be both inline or block-level. - # These will be treated block-level when the start tag is alone on - # its line, otherwise they're not matched here and will be taken as - # inline later. - # * List "b" is made of tags which are always block-level; - # - $block_tags_a_re = 'ins|del'; - $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|style|form|fieldset|iframe|math|svg|'. - 'article|section|nav|aside|hgroup|header|footer|'. - 'figure'; - - # Regular expression for the content of a block tag. - $nested_tags_level = 4; - $attr = ' - (?> # optional tag attributes - \s # starts with whitespace - (?> - [^>"/]+ # text outside quotes - | - /+(?!>) # slash not followed by ">" - | - "[^"]*" # text inside double quotes (tolerate ">") - | - \'[^\']*\' # text inside single quotes (tolerate ">") - )* - )? - '; - $content = - str_repeat(' - (?> - [^<]+ # content without tag - | - <\2 # nested opening tag - '.$attr.' # attributes - (?> - /> - | - >', $nested_tags_level). # end of opening tag - '.*?'. # last level nested tag content - str_repeat(' - # closing nested tag - ) - | - <(?!/\2\s*> # other tags with a different name - ) - )*', - $nested_tags_level); - $content2 = str_replace('\2', '\3', $content); - - # First, look for nested blocks, e.g.: - #

- #
- # tags for inner block must be indented. - #
- #
- # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `
` and stop at the first `
`. - $text = preg_replace_callback('{(?> - (?> - (?<=\n) # Starting on its own line - | # or - \A\n? # the at beginning of the doc - ) - ( # save in $1 - - # Match from `\n` to `\n`, handling nested tags - # in between. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_b_re.')# start tag = $2 - '.$attr.'> # attributes followed by > and \n - '.$content.' # content, support nesting - # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special version for tags of group a. - - [ ]{0,'.$less_than_tab.'} - <('.$block_tags_a_re.')# start tag = $3 - '.$attr.'>[ ]*\n # attributes followed by > - '.$content2.' # content, support nesting - # the matching end tag - [ ]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - - | # Special case just for
. It was easier to make a special - # case than to make the other regex more complicated. - - [ ]{0,'.$less_than_tab.'} - <(hr) # start tag = $2 - '.$attr.' # attributes - /?> # the matching end tag - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # Special case for standalone HTML comments: - - [ ]{0,'.$less_than_tab.'} - (?s: - - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - | # PHP and ASP-style processor instructions ( - ) - [ ]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - - ) - )}Sxmi', - array($this, '_hashHTMLBlocks_callback'), - $text); - - return $text; - } - protected function _hashHTMLBlocks_callback($matches) { - $text = $matches[1]; - $key = $this->hashBlock($text); - return "\n\n$key\n\n"; - } - - - protected function hashPart($text, $boundary = 'X') { - # - # Called whenever a tag must be hashed when a function insert an atomic - # element in the text stream. Passing $text to through this function gives - # a unique text-token which will be reverted back when calling unhash. - # - # The $boundary argument specify what character should be used to surround - # the token. By convension, "B" is used for block elements that needs not - # to be wrapped into paragraph tags at the end, ":" is used for elements - # that are word separators and "X" is used in the general case. - # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. - $text = $this->unhash($text); - - # Then hash the block. - static $i = 0; - $key = "$boundary\x1A" . ++$i . $boundary; - $this->html_hashes[$key] = $text; - return $key; # String that will replace the tag. - } - - - protected function hashBlock($text) { - # - # Shortcut function for hashPart with block-level boundaries. - # - return $this->hashPart($text, 'B'); - } - - - protected $block_gamut = array( - # - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - # - "doHeaders" => 10, - "doHorizontalRules" => 20, - - "doLists" => 40, - "doCodeBlocks" => 50, - "doBlockQuotes" => 60, - ); - - protected function runBlockGamut($text) { - # - # Run block gamut tranformations. - # - # We need to escape raw HTML in Markdown source before doing anything - # else. This need to be done for each block, and not only at the - # begining in the Markdown function since hashed blocks can be part of - # list items and could have been indented. Indented blocks would have - # been seen as a code block in a previous pass of hashHTMLBlocks. - $text = $this->hashHTMLBlocks($text); - - return $this->runBasicBlockGamut($text); - } - - protected function runBasicBlockGamut($text) { - # - # Run block gamut tranformations, without hashing HTML blocks. This is - # useful when HTML blocks are known to be already hashed, like in the first - # whole-document pass. - # - foreach ($this->block_gamut as $method => $priority) { - $text = $this->$method($text); - } - - # Finally form paragraph and restore hashed blocks. - $text = $this->formParagraphs($text); - - return $text; - } - - - protected function doHorizontalRules($text) { - # Do Horizontal Rules: - return preg_replace( - '{ - ^[ ]{0,3} # Leading space - ([-*_]) # $1: First marker - (?> # Repeated marker group - [ ]{0,2} # Zero, one, or two spaces. - \1 # Marker character - ){2,} # Group repeated at least twice - [ ]* # Tailing spaces - $ # End of line. - }mx', - "\n".$this->hashBlock("empty_element_suffix")."\n", - $text); - } - - - protected $span_gamut = array( - # - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - # - # Process character escapes, code spans, and inline HTML - # in one shot. - "parseSpan" => -30, - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - "doImages" => 10, - "doAnchors" => 20, - - # Make links out of things like `` - # Must come after doAnchors, because you can use < and > - # delimiters in inline links like [this](). - "doAutoLinks" => 30, - "encodeAmpsAndAngles" => 40, - - "doItalicsAndBold" => 50, - "doHardBreaks" => 60, - ); - - protected function runSpanGamut($text) { - # - # Run span gamut tranformations. - # - foreach ($this->span_gamut as $method => $priority) { - $text = $this->$method($text); - } - - return $text; - } - - - protected function doHardBreaks($text) { - # Do hard breaks: - return preg_replace_callback('/ {2,}\n/', - array($this, '_doHardBreaks_callback'), $text); - } - protected function _doHardBreaks_callback($matches) { - return $this->hashPart("empty_element_suffix\n"); - } - - - protected function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML tags. - # - if ($this->in_anchor) return $text; - $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }xs', - array($this, '_doAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - \( # literal paren - [ \n]* - (?: - <(.+?)> # href = $3 - | - ('.$this->nested_url_parenthesis_re.') # href = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # Title = $7 - \6 # matching quote - [ \n]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - ) - }xs', - array($this, '_doAnchors_inline_callback'), $text); - - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ([^\[\]]+) # link text = $2; can\'t contain [ or ] - \] - ) - }xs', - array($this, '_doAnchors_reference_callback'), $text); - - $this->in_anchor = false; - return $text; - } - protected function _doAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id =& $matches[3]; - - if ($link_id == "") { - # for shortcut links like [this][] or [this]. - $link_id = $link_text; - } - - # lower-case and turn embedded newlines into spaces - $link_id = strtolower($link_id); - $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); - - if (isset($this->urls[$link_id])) { - $url = $this->urls[$link_id]; - $url = $this->encodeURLAttribute($url); - - $result = "titles[$link_id] ) ) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - $result = $this->hashPart($result); - } - else { - $result = $whole_match; - } - return $result; - } - protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - // if the URL was of the form it got caught by the HTML - // tag parser and hashed. Need to reverse the process before using the URL. - $unhashed = $this->unhash($url); - if ($unhashed != $url) - $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); - - $url = $this->encodeURLAttribute($url); - - $result = "encodeAttribute($title); - $result .= " title=\"$title\""; - } - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - - return $this->hashPart($result); - } - - - protected function doImages($text) { - # - # Turn Markdown image shortcuts into tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array($this, '_doImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - \s? # One optional whitespace character - \( # literal paren - [ \n]* - (?: - <(\S*)> # src url = $3 - | - ('.$this->nested_url_parenthesis_re.') # src url = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # title = $7 - \6 # matching quote - [ \n]* - )? # title is optional - \) - ) - }xs', - array($this, '_doImages_inline_callback'), $text); - - return $text; - } - protected function _doImages_reference_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. - } - - $alt_text = $this->encodeAttribute($alt_text); - if (isset($this->urls[$link_id])) { - $url = $this->encodeURLAttribute($this->urls[$link_id]); - $result = "\"$alt_text\"";titles[$link_id])) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - $result .= $this->empty_element_suffix; - $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - protected function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - - $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeURLAttribute($url); - $result = "\"$alt_text\"";encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $this->empty_element_suffix; - - return $this->hashPart($result); - } - - - protected function doHeaders($text) { - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', - array($this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - \n+ - }xm', - array($this, '_doHeaders_callback_atx'), $text); - - return $text; - } - protected function _doHeaders_callback_setext($matches) { - # Terrible hack to check we haven't found an empty list item. - if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) - return $matches[0]; - - $level = $matches[2]{0} == '=' ? 1 : 2; - $block = "".$this->runSpanGamut($matches[1]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - protected function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $block = "".$this->runSpanGamut($matches[2]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - protected function doLists($text) { - # - # Form HTML ordered (numbered) and unordered (bulleted) lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[\.]'; - - $markers_relist = array( - $marker_ul_re => $marker_ol_re, - $marker_ol_re => $marker_ul_re, - ); - - foreach ($markers_relist as $marker_re => $other_marker_re) { - # Re-usable pattern to match any entirel ul or ol list: - $whole_list_re = ' - ( # $1 = whole list - ( # $2 - ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces - ('.$marker_re.') # $4 = first list item marker - [ ]+ - ) - (?s:.+?) - ( # $5 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ ]* - '.$marker_re.'[ ]+ - ) - | - (?= # Lookahead for another kind of list - \n - \3 # Must have the same indentation - '.$other_marker_re.'[ ]+ - ) - ) - ) - '; // mx - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - - if ($this->list_level) { - $text = preg_replace_callback('{ - ^ - '.$whole_list_re.' - }mx', - array($this, '_doLists_callback'), $text); - } - else { - $text = preg_replace_callback('{ - (?:(?<=\n)\n|\A\n?) # Must eat the newline - '.$whole_list_re.' - }mx', - array($this, '_doLists_callback'), $text); - } - } - - return $text; - } - protected function _doLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $marker_ul_re = '[*+-]'; - $marker_ol_re = '\d+[\.]'; - $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; - - $list = $matches[1]; - $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol"; - - $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); - - $list .= "\n"; - $result = $this->processListItems($list, $marker_any_re); - - $result = $this->hashBlock("<$list_type>\n" . $result . ""); - return "\n". $result ."\n\n"; - } - - protected $list_level = 0; - - protected function processListItems($list_str, $marker_any_re) { - # - # Process the contents of a single ordered or unordered list, splitting it - # into individual list items. - # - # The $this->list_level global keeps track of when we're inside a list. - # Each time we enter a list, we increment it; when we leave a list, - # we decrement. If it's zero, we're not in a list anymore. - # - # We do this because when we're not inside a list, we want to treat - # something like this: - # - # I recommend upgrading to version - # 8. Oops, now this line is treated - # as a sub-list. - # - # As a single paragraph, despite the fact that the second line starts - # with a digit-period-space sequence. - # - # Whereas when we're inside a list (or sub-list), that line will be - # treated as the start of a sub-list. What a kludge, huh? This is - # an aspect of Markdown's syntax that's hard to parse perfectly - # without resorting to mind-reading. Perhaps the solution is to - # change the syntax rules such that sub-lists must start with a - # starting cardinal number; e.g. "1." or "a.". - - $this->list_level++; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - $list_str = preg_replace_callback('{ - (\n)? # leading line = $1 - (^[ ]*) # leading whitespace = $2 - ('.$marker_any_re.' # list marker and space = $3 - (?:[ ]+|(?=\n)) # space only required if item is not empty - ) - ((?s:.*?)) # list item text = $4 - (?:(\n+(?=\n))|\n) # tailing blank line = $5 - (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) - }xm', - array($this, '_processListItems_callback'), $list_str); - - $this->list_level--; - return $list_str; - } - protected function _processListItems_callback($matches) { - $item = $matches[4]; - $leading_line =& $matches[1]; - $leading_space =& $matches[2]; - $marker_space = $matches[3]; - $tailing_blank_line =& $matches[5]; - - if ($leading_line || $tailing_blank_line || - preg_match('/\n{2,}/', $item)) - { - # Replace marker with the appropriate whitespace indentation - $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; - $item = $this->runBlockGamut($this->outdent($item)."\n"); - } - else { - # Recursion for sub-lists: - $item = $this->doLists($this->outdent($item)); - $item = preg_replace('/\n+$/', '', $item); - $item = $this->runSpanGamut($item); - } - - return "
  • " . $item . "
  • \n"; - } - - - protected function doCodeBlocks($text) { - # - # Process Markdown `
    ` blocks.
    -	#
    -		$text = preg_replace_callback('{
    -				(?:\n\n|\A\n?)
    -				(	            # $1 = the code block -- one or more lines, starting with a space/tab
    -				  (?>
    -					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
    -					.*\n+
    -				  )+
    -				)
    -				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
    -			}xm',
    -			array($this, '_doCodeBlocks_callback'), $text);
    -
    -		return $text;
    -	}
    -	protected function _doCodeBlocks_callback($matches) {
    -		$codeblock = $matches[1];
    -
    -		$codeblock = $this->outdent($codeblock);
    -		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
    -
    -		# trim leading newlines and trailing newlines
    -		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
    -
    -		$codeblock = "
    $codeblock\n
    "; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - - - protected function makeCodeSpan($code) { - # - # Create a code span markup for $code. Called from handleSpanToken. - # - $code = htmlspecialchars(trim($code), ENT_NOQUOTES); - return $this->hashPart("$code"); - } - - - protected $em_relist = array( - '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(?em_relist as $em => $em_re) { - foreach ($this->strong_relist as $strong => $strong_re) { - # Construct list of allowed token expressions. - $token_relist = array(); - if (isset($this->em_strong_relist["$em$strong"])) { - $token_relist[] = $this->em_strong_relist["$em$strong"]; - } - $token_relist[] = $em_re; - $token_relist[] = $strong_re; - - # Construct master expression from list. - $token_re = '{('. implode('|', $token_relist) .')}'; - $this->em_strong_prepared_relist["$em$strong"] = $token_re; - } - } - } - - protected function doItalicsAndBold($text) { - $token_stack = array(''); - $text_stack = array(''); - $em = ''; - $strong = ''; - $tree_char_em = false; - - while (1) { - # - # Get prepared regular expression for seraching emphasis tokens - # in current context. - # - $token_re = $this->em_strong_prepared_relist["$em$strong"]; - - # - # Each loop iteration search for the next emphasis token. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - $text_stack[0] .= $parts[0]; - $token =& $parts[1]; - $text =& $parts[2]; - - if (empty($token)) { - # Reached end of text span: empty stack without emitting. - # any more emphasis. - while ($token_stack[0]) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - break; - } - - $token_len = strlen($token); - if ($tree_char_em) { - # Reached closing marker while inside a three-char emphasis. - if ($token_len == 3) { - # Three-char closing marker, close em and strong. - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - $strong = ''; - } else { - # Other closing marker: close one em or strong and - # change current token state to match the other - $token_stack[0] = str_repeat($token{0}, 3-$token_len); - $tag = $token_len == 2 ? "strong" : "em"; - $span = $text_stack[0]; - $span = $this->runSpanGamut($span); - $span = "<$tag>$span"; - $text_stack[0] = $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - $tree_char_em = false; - } else if ($token_len == 3) { - if ($em) { - # Reached closing marker for both em and strong. - # Closing strong marker: - for ($i = 0; $i < 2; ++$i) { - $shifted_token = array_shift($token_stack); - $tag = strlen($shifted_token) == 2 ? "strong" : "em"; - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "<$tag>$span"; - $text_stack[0] .= $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong - } - } else { - # Reached opening three-char emphasis marker. Push on token - # stack; will be handled by the special condition above. - $em = $token{0}; - $strong = "$em$em"; - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $tree_char_em = true; - } - } else if ($token_len == 2) { - if ($strong) { - # Unwind any dangling emphasis marker: - if (strlen($token_stack[0]) == 1) { - $text_stack[1] .= array_shift($token_stack); - $text_stack[0] .= array_shift($text_stack); - } - # Closing strong marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $strong = ''; - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $strong = $token; - } - } else { - # Here $token_len == 1 - if ($em) { - if (strlen($token_stack[0]) == 1) { - # Closing emphasis marker: - array_shift($token_stack); - $span = array_shift($text_stack); - $span = $this->runSpanGamut($span); - $span = "$span"; - $text_stack[0] .= $this->hashPart($span); - $em = ''; - } else { - $text_stack[0] .= $token; - } - } else { - array_unshift($token_stack, $token); - array_unshift($text_stack, ''); - $em = $token; - } - } - } - return $text_stack[0]; - } - - - protected function doBlockQuotes($text) { - $text = preg_replace_callback('/ - ( # Wrap whole match in $1 - (?> - ^[ ]*>[ ]? # ">" at the start of a line - .+\n # rest of the first line - (.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - ) - /xm', - array($this, '_doBlockQuotes_callback'), $text); - - return $text; - } - protected function _doBlockQuotes_callback($matches) { - $bq = $matches[1]; - # trim one level of quoting - trim whitespace-only lines - $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); - $bq = $this->runBlockGamut($bq); # recurse - - $bq = preg_replace('/^/m', " ", $bq); - # These leading spaces cause problem with
     content, 
    -		# so we need to fix that:
    -		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', - array($this, '_doBlockQuotes_callback2'), $bq); - - return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; - } - protected function _doBlockQuotes_callback2($matches) { - $pre = $matches[1]; - $pre = preg_replace('/^ /m', '', $pre); - return $pre; - } - - - protected function formParagraphs($text) { - # - # Params: - # $text - string to process with html

    tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap

    tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { - # Is a paragraph. - $value = $this->runSpanGamut($value); - $value = preg_replace('/^([ ]*)/', "

    ", $value); - $value .= "

    "; - $grafs[$key] = $this->unhash($value); - } - else { - # Is a block. - # Modify elements of @grafs in-place... - $graf = $value; - $block = $this->html_hashes[$graf]; - $graf = $block; -// if (preg_match('{ -// \A -// ( # $1 =
    tag -//
    ]* -// \b -// markdown\s*=\s* ([\'"]) # $2 = attr quote char -// 1 -// \2 -// [^>]* -// > -// ) -// ( # $3 = contents -// .* -// ) -// (
    ) # $4 = closing tag -// \z -// }xs', $block, $matches)) -// { -// list(, $div_open, , $div_content, $div_close) = $matches; -// -// # We can't call Markdown(), because that resets the hash; -// # that initialization code should be pulled into its own sub, though. -// $div_content = $this->hashHTMLBlocks($div_content); -// -// # Run document gamut methods on the content. -// foreach ($this->document_gamut as $method => $priority) { -// $div_content = $this->$method($div_content); -// } -// -// $div_open = preg_replace( -// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); -// -// $graf = $div_open . "\n" . $div_content . "\n" . $div_close; -// } - $grafs[$key] = $graf; - } - } - - return implode("\n\n", $grafs); - } - - - protected function encodeAttribute($text) { - # - # Encode text for a double-quoted HTML attribute. This function - # is *not* suitable for attributes enclosed in single quotes. - # - $text = $this->encodeAmpsAndAngles($text); - $text = str_replace('"', '"', $text); - return $text; - } - - - protected function encodeURLAttribute($url, &$text = null) { - # - # Encode text for a double-quoted HTML attribute containing a URL, - # applying the URL filter if set. Also generates the textual - # representation for the URL (removing mailto: or tel:) storing it in $text. - # This function is *not* suitable for attributes enclosed in single quotes. - # - if ($this->url_filter_func) - $url = call_user_func($this->url_filter_func, $url); - - if (preg_match('{^mailto:}i', $url)) - $url = $this->encodeEntityObfuscatedAttribute($url, $text, 7); - else if (preg_match('{^tel:}i', $url)) - { - $url = $this->encodeAttribute($url); - $text = substr($url, 4); - } - else - { - $url = $this->encodeAttribute($url); - $text = $url; - } - - return $url; - } - - - protected function encodeAmpsAndAngles($text) { - # - # Smart processing for ampersands and angle brackets that need to - # be encoded. Valid character entities are left alone unless the - # no-entities mode is set. - # - if ($this->no_entities) { - $text = str_replace('&', '&', $text); - } else { - # Ampersand-encoding based entirely on Nat Irons's Amputator - # MT plugin: - $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', - '&', $text); - } - # Encode remaining <'s - $text = str_replace('<', '<', $text); - - return $text; - } - - - protected function doAutoLinks($text) { - $text = preg_replace_callback('{<((https?|ftp|dict|tel):[^\'">\s]+)>}i', - array($this, '_doAutoLinks_url_callback'), $text); - - # Email addresses: - $text = preg_replace_callback('{ - < - (?:mailto:)? - ( - (?: - [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+ - | - ".*?" - ) - \@ - (?: - [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ - | - \[[\d.a-fA-F:]+\] # IPv4 & IPv6 - ) - ) - > - }xi', - array($this, '_doAutoLinks_email_callback'), $text); - - return $text; - } - protected function _doAutoLinks_url_callback($matches) { - $url = $this->encodeURLAttribute($matches[1], $text); - $link = "$text"; - return $this->hashPart($link); - } - protected function _doAutoLinks_email_callback($matches) { - $addr = $matches[1]; - $url = $this->encodeURLAttribute("mailto:$addr", $text); - $link = "$text"; - return $this->hashPart($link); - } - - - protected function encodeEntityObfuscatedAttribute($text, &$tail = null, $head_length = 0) { - # - # Input: some text to obfuscate, e.g. "mailto:foo@example.com" - # - # Output: the same text but with most characters encoded as either a - # decimal or hex entity, in the hopes of foiling most address - # harvesting spam bots. E.g.: - # - # mailto:foo - # @example.co - # m - # - # Note: the additional output $tail is assigned the same value as the - # ouput, minus the number of characters specified by $head_length. - # - # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - # With some optimizations by Milian Wolff. Forced encoding of HTML - # attribute special characters by Allan Odgaard. - # - if ($text == "") return $tail = ""; - - $chars = preg_split('/(? $char) { - $ord = ord($char); - # Ignore non-ascii chars. - if ($ord < 128) { - $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. - # roughly 10% raw, 45% hex, 45% dec - # '@' *must* be encoded. I insist. - # '"' and '>' have to be encoded inside the attribute - if ($r > 90 && strpos('@"&>', $char) === false) /* do nothing */; - else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; - else $chars[$key] = '&#'.$ord.';'; - } - } - - $text = implode('', $chars); - $tail = $head_length ? implode('', array_slice($chars, $head_length)) : $text; - - return $text; - } - - - protected function parseSpan($str) { - # - # Take the string $str and parse it into tokens, hashing embeded HTML, - # escaped characters and handling code spans. - # - $output = ''; - - $span_re = '{ - ( - \\\\'.$this->escape_chars_re.' - | - (?no_markup ? '' : ' - | - # comment - | - <\?.*?\?> | <%.*?%> # processing instruction - | - <[!$]?[-a-zA-Z0-9:_]+ # regular tags - (?> - \s - (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* - )? - > - | - <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag - | - # closing tag - ').' - ) - }xs'; - - while (1) { - # - # Each loop iteration seach for either the next tag, the next - # openning code span marker, or the next escaped character. - # Each token is then passed to handleSpanToken. - # - $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); - - # Create token from text preceding tag. - if ($parts[0] != "") { - $output .= $parts[0]; - } - - # Check if we reach the end. - if (isset($parts[1])) { - $output .= $this->handleSpanToken($parts[1], $parts[2]); - $str = $parts[2]; - } - else { - break; - } - } - - return $output; - } - - - protected function handleSpanToken($token, &$str) { - # - # Handle $token provided by parseSpan by determining its nature and - # returning the corresponding value that should replace it. - # - switch ($token{0}) { - case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); - case "`": - # Search for end marker in remaining text. - if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', - $str, $matches)) - { - $str = $matches[2]; - $codespan = $this->makeCodeSpan($matches[1]); - return $this->hashPart($codespan); - } - return $token; // return as text since no ending marker found. - default: - return $this->hashPart($token); - } - } - - - protected function outdent($text) { - # - # Remove one level of line-leading tabs or spaces - # - return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); - } - - - # String length function for detab. `_initDetab` will create a function to - # hanlde UTF-8 if the default function does not exist. - protected $utf8_strlen = 'mb_strlen'; - - protected function detab($text) { - # - # Replace tabs with the appropriate amount of space. - # - # For each line we separate the line in blocks delemited by - # tab characters. Then we reconstruct every line by adding the - # appropriate number of space between each blocks. - - $text = preg_replace_callback('/^.*\t.*$/m', - array($this, '_detab_callback'), $text); - - return $text; - } - protected function _detab_callback($matches) { - $line = $matches[0]; - $strlen = $this->utf8_strlen; # strlen function for UTF-8. - - # Split in blocks. - $blocks = explode("\t", $line); - # Add each blocks to the line. - $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. - foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->tab_width - - $strlen($line, 'UTF-8') % $this->tab_width; - $line .= str_repeat(" ", $amount) . $block; - } - return $line; - } - protected function _initDetab() { - # - # Check for the availability of the function in the `utf8_strlen` property - # (initially `mb_strlen`). If the function is not available, create a - # function that will loosely count the number of UTF-8 characters with a - # regular expression. - # - if (function_exists($this->utf8_strlen)) return; - $this->utf8_strlen = create_function('$text', 'return preg_match_all( - "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", - $text, $m);'); - } - - - protected function unhash($text) { - # - # Swap back in all the tags hashed by _HashHTMLBlocks. - # - return preg_replace_callback('/(.)\x1A[0-9]+\1/', - array($this, '_unhash_callback'), $text); - } - protected function _unhash_callback($matches) { - return $this->html_hashes[$matches[0]]; - } - -} - - -# -# Temporary Markdown Extra Parser Implementation Class -# -# NOTE: DON'T USE THIS CLASS -# Currently the implementation of of Extra resides here in this temporary class. -# This makes it easier to propagate the changes between the three different -# packaging styles of PHP Markdown. When this issue is resolved, this -# MarkdownExtra_TmpImpl class here will disappear and \Michelf\MarkdownExtra -# will contain the code. So please use \Michelf\MarkdownExtra and ignore this -# one. -# - -abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown { - - ### Configuration Variables ### - - # Prefix for footnote ids. - public $fn_id_prefix = ""; - - # Optional title attribute for footnote links and backlinks. - public $fn_link_title = ""; - public $fn_backlink_title = ""; - - # Optional class attribute for footnote links and backlinks. - public $fn_link_class = "footnote-ref"; - public $fn_backlink_class = "footnote-backref"; - - # Class name for table cell alignment (%% replaced left/center/right) - # For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center' - # If empty, the align attribute is used instead of a class name. - public $table_align_class_tmpl = ''; - - # Optional class prefix for fenced code block. - public $code_class_prefix = ""; - # Class attribute for code blocks goes on the `code` tag; - # setting this to true will put attributes on the `pre` tag instead. - public $code_attr_on_pre = false; - - # Predefined abbreviations. - public $predef_abbr = array(); - - - ### Parser Implementation ### - - public function __construct() { - # - # Constructor function. Initialize the parser object. - # - # Add extra escapable characters before parent constructor - # initialize the table. - $this->escape_chars .= ':|'; - - # Insert extra document, block, and span transformations. - # Parent constructor will do the sorting. - $this->document_gamut += array( - "doFencedCodeBlocks" => 5, - "stripFootnotes" => 15, - "stripAbbreviations" => 25, - "appendFootnotes" => 50, - ); - $this->block_gamut += array( - "doFencedCodeBlocks" => 5, - "doTables" => 15, - "doDefLists" => 45, - ); - $this->span_gamut += array( - "doFootnotes" => 5, - "doAbbreviations" => 70, - ); - - parent::__construct(); - } - - - # Extra variables used during extra transformations. - protected $footnotes = array(); - protected $footnotes_ordered = array(); - protected $footnotes_ref_count = array(); - protected $footnotes_numbers = array(); - protected $abbr_desciptions = array(); - protected $abbr_word_re = ''; - - # Give the current footnote number. - protected $footnote_counter = 1; - - - protected function setup() { - # - # Setting up Extra-specific variables. - # - parent::setup(); - - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->footnotes_ref_count = array(); - $this->footnotes_numbers = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - $this->footnote_counter = 1; - - foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - } - } - - protected function teardown() { - # - # Clearing Extra-specific variables. - # - $this->footnotes = array(); - $this->footnotes_ordered = array(); - $this->footnotes_ref_count = array(); - $this->footnotes_numbers = array(); - $this->abbr_desciptions = array(); - $this->abbr_word_re = ''; - - parent::teardown(); - } - - - ### Extra Attribute Parser ### - - # Expression to use to catch attributes (includes the braces) - protected $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}'; - # Expression to use when parsing in a context when no capture is desired - protected $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}'; - - protected function doExtraAttributes($tag_name, $attr) { - # - # Parse attributes caught by the $this->id_class_attr_catch_re expression - # and return the HTML-formatted list of attributes. - # - # Currently supported attributes are .class and #id. - # - if (empty($attr)) return ""; - - # Split on components - preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches); - $elements = $matches[0]; - - # handle classes and ids (only first id taken into account) - $classes = array(); - $id = false; - foreach ($elements as $element) { - if ($element{0} == '.') { - $classes[] = substr($element, 1); - } else if ($element{0} == '#') { - if ($id === false) $id = substr($element, 1); - } - } - - # compose attributes as string - $attr_str = ""; - if (!empty($id)) { - $attr_str .= ' id="'.$id.'"'; - } - if (!empty($classes)) { - $attr_str .= ' class="'.implode(" ", $classes).'"'; - } - return $attr_str; - } - - - protected function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ ]* - \n? # maybe *one* newline - [ ]* - (?: - <(.+?)> # url = $2 - | - (\S+?) # url = $3 - ) - [ ]* - \n? # maybe one newline - [ ]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.*?) # title = $4 - [")] - [ ]* - )? # title is optional - (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr - (?:\n+|\Z) - }xm', - array($this, '_stripLinkDefinitions_callback'), - $text); - return $text; - } - protected function _stripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $url = $matches[2] == '' ? $matches[3] : $matches[2]; - $this->urls[$link_id] = $url; - $this->titles[$link_id] =& $matches[4]; - $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]); - return ''; # String that will replace the block - } - - - ### HTML Block Parser ### - - # Tags that are always treated as block tags: - protected $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption|figure'; - - # Tags treated as block tags only if the opening tag is alone on its line: - protected $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; - - # Tags where markdown="1" default to span mode: - protected $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; - - # Tags which must not have their contents modified, no matter where - # they appear: - protected $clean_tags_re = 'script|style|math|svg'; - - # Tags that do not need to be closed. - protected $auto_close_tags_re = 'hr|img|param|source|track'; - - - protected function hashHTMLBlocks($text) { - # - # Hashify HTML Blocks and "clean tags". - # - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

    s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded. - # - # This works by calling _HashHTMLBlocks_InMarkdown, which then calls - # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back - # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. - # These two functions are calling each other. It's recursive! - # - if ($this->no_markup) return $text; - - # - # Call the HTML-in-Markdown hasher. - # - list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text); - - return $text; - } - protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0, - $enclosing_tag_re = '', $span = false) - { - # - # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. - # - # * $indent is the number of space to be ignored when checking for code - # blocks. This is important because if we don't take the indent into - # account, something like this (which looks right) won't work as expected: - # - #

    - #
    - # Hello World. <-- Is this a Markdown code block or text? - #
    <-- Is this a Markdown code block or a real tag? - #
    - # - # If you don't like this, just don't indent the tag on which - # you apply the markdown="1" attribute. - # - # * If $enclosing_tag_re is not empty, stops at the first unmatched closing - # tag with that name. Nested tags supported. - # - # * If $span is true, text inside must treated as span. So any double - # newline will be replaced by a single newline so that it does not create - # paragraphs. - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to check for the presense of newlines around a block tag. - $newline_before_re = '/(?:^\n?|\n\n)*$/'; - $newline_after_re = - '{ - ^ # Start of text following the tag. - (?>[ ]*)? # Optional comment. - [ ]*\n # Must be followed by newline. - }xs'; - - # Regex to match any tag. - $block_tag_re = - '{ - ( # $2: Capture whole tag. - # Tag name. - '.$this->block_tags_re.' | - '.$this->context_block_tags_re.' | - '.$this->clean_tags_re.' | - (?!\s)'.$enclosing_tag_re.' - ) - (?: - (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. - (?> - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - # CData Block - '. ( !$span ? ' # If not in span. - | - # Indented code block - (?: ^[ ]*\n | ^ | \n[ ]*\n ) - [ ]{'.($indent+4).'}[^\n]* \n - (?> - (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n - )* - | - # Fenced code block marker - (?<= ^ | \n ) - [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,}) - [ ]* - (?: - \.?[-_:a-zA-Z0-9]+ # standalone class name - | - '.$this->id_class_attr_nocatch_re.' # extra attributes - )? - [ ]* - (?= \n ) - ' : '' ). ' # End (if not is span). - | - # Code span marker - # Note, this regex needs to go after backtick fenced - # code blocks but it should also be kept outside of the - # "if not in span" condition adding backticks to the parser - `+ - ) - }xs'; - - - $depth = 0; # Current depth inside the tag tree. - $parsed = ""; # Parsed text that will be returned. - - # - # Loop through every tag until we find the closing tag of the parent - # or loop until reaching the end of text if no parent tag specified. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($block_tag_re, $text, 2, - PREG_SPLIT_DELIM_CAPTURE); - - # If in Markdown span mode, add a empty-string span-level hash - # after each newline to prevent triggering any block element. - if ($span) { - $void = $this->hashPart("", ':'); - $newline = "$void\n"; - $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; - } - - $parsed .= $parts[0]; # Text before current tag. - - # If end of $text has been reached. Stop loop. - if (count($parts) < 3) { - $text = ""; - break; - } - - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - $tag_re = preg_quote($tag); # For use in a regular expression. - - # - # Check for: Fenced code block marker. - # Note: need to recheck the whole tag to disambiguate backtick - # fences from code spans - # - if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) { - # Fenced code block marker: find matching end marker. - $fence_indent = strlen($capture[1]); # use captured indent in re - $fence_re = $capture[2]; # use captured fence in re - if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text, - $matches)) - { - # End marker found: pass text unchanged until marker. - $parsed .= $tag . $matches[0]; - $text = substr($text, strlen($matches[0])); - } - else { - # No end marker: just skip it. - $parsed .= $tag; - } - } - # - # Check for: Indented code block. - # - else if ($tag{0} == "\n" || $tag{0} == " ") { - # Indented code block: pass it unchanged, will be handled - # later. - $parsed .= $tag; - } - # - # Check for: Code span marker - # Note: need to check this after backtick fenced code blocks - # - else if ($tag{0} == "`") { - # Find corresponding end marker. - $tag_re = preg_quote($tag); - if (preg_match('{^(?>.+?|\n(?!\n))*?(?block_tags_re.')\b}', $tag) || - ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && - preg_match($newline_before_re, $parsed) && - preg_match($newline_after_re, $text) ) - ) - { - # Need to parse tag and following text using the HTML parser. - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); - - # Make sure it stays outside of any paragraph by adding newlines. - $parsed .= "\n\n$block_text\n\n"; - } - # - # Check for: Clean tag (like script, math) - # HTML Comments, processing instructions. - # - else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Need to parse tag and following text using the HTML parser. - # (don't check for markdown attribute) - list($block_text, $text) = - $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); - - $parsed .= $block_text; - } - # - # Check for: Tag with same name as enclosing tag. - # - else if ($enclosing_tag_re !== '' && - # Same name as enclosing tag. - preg_match('{^= 0); - - return array($parsed, $text); - } - protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { - # - # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. - # - # * Calls $hash_method to convert any blocks. - # * Stops when the first opening tag closes. - # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. - # (it is not inside clean tags) - # - # Returns an array of that form: ( processed text , remaining text ) - # - if ($text === '') return array('', ''); - - # Regex to match `markdown` attribute inside of a tag. - $markdown_attr_re = ' - { - \s* # Eat whitespace before the `markdown` attribute - markdown - \s*=\s* - (?> - (["\']) # $1: quote delimiter - (.*?) # $2: attribute value - \1 # matching delimiter - | - ([^\s>]*) # $3: unquoted attribute value - ) - () # $4: make $3 always defined (avoid warnings) - }xs'; - - # Regex to match any tag. - $tag_re = '{ - ( # $2: Capture whole tag. - - ".*?" | # Double quotes (can contain `>`) - \'.*?\' | # Single quotes (can contain `>`) - .+? # Anything but quotes and `>`. - )*? - )? - > # End of tag. - | - # HTML Comment - | - <\?.*?\?> | <%.*?%> # Processing instruction - | - # CData Block - ) - }xs'; - - $original_text = $text; # Save original text in case of faliure. - - $depth = 0; # Current depth inside the tag tree. - $block_text = ""; # Temporary text holder for current text. - $parsed = ""; # Parsed text that will be returned. - - # - # Get the name of the starting tag. - # (This pattern makes $base_tag_name_re safe without quoting.) - # - if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) - $base_tag_name_re = $matches[1]; - - # - # Loop through every tag until we find the corresponding closing tag. - # - do { - # - # Split the text using the first $tag_match pattern found. - # Text before pattern will be first in the array, text after - # pattern will be at the end, and between will be any catches made - # by the pattern. - # - $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - - if (count($parts) < 3) { - # - # End of $text reached with unbalenced tag(s). - # In that case, we return original text unchanged and pass the - # first character as filtered to prevent an infinite loop in the - # parent function. - # - return array($original_text{0}, substr($original_text, 1)); - } - - $block_text .= $parts[0]; # Text before current tag. - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - - # - # Check for: Auto-close tag (like
    ) - # Comments and Processing Instructions. - # - if (preg_match('{^auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') - { - # Just add the tag to the block as if it was text. - $block_text .= $tag; - } - else { - # - # Increase/decrease nested tag count. Only do so if - # the tag's name match base tag's. - # - if (preg_match('{^mode = $attr_m[2] . $attr_m[3]; - $span_mode = $this->mode == 'span' || $this->mode != 'block' && - preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); - - # Calculate indent before tag. - if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { - $strlen = $this->utf8_strlen; - $indent = $strlen($matches[1], 'UTF-8'); - } else { - $indent = 0; - } - - # End preceding block with this tag. - $block_text .= $tag; - $parsed .= $this->$hash_method($block_text); - - # Get enclosing tag name for the ParseMarkdown function. - # (This pattern makes $tag_name_re safe without quoting.) - preg_match('/^<([\w:$]*)\b/', $tag, $matches); - $tag_name_re = $matches[1]; - - # Parse the content using the HTML-in-Markdown parser. - list ($block_text, $text) - = $this->_hashHTMLBlocks_inMarkdown($text, $indent, - $tag_name_re, $span_mode); - - # Outdent markdown text. - if ($indent > 0) { - $block_text = preg_replace("/^[ ]{1,$indent}/m", "", - $block_text); - } - - # Append tag content to parsed text. - if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; - else $parsed .= "$block_text"; - - # Start over with a new block. - $block_text = ""; - } - else $block_text .= $tag; - } - - } while ($depth > 0); - - # - # Hash last block text that wasn't processed inside the loop. - # - $parsed .= $this->$hash_method($block_text); - - return array($parsed, $text); - } - - - protected function hashClean($text) { - # - # Called whenever a tag must be hashed when a function inserts a "clean" tag - # in $text, it passes through this function and is automaticaly escaped, - # blocking invalid nested overlap. - # - return $this->hashPart($text, 'C'); - } - - - protected function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML tags. - # - if ($this->in_anchor) return $text; - $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - ) - }xs', - array($this, '_doAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ('.$this->nested_brackets_re.') # link text = $2 - \] - \( # literal paren - [ \n]* - (?: - <(.+?)> # href = $3 - | - ('.$this->nested_url_parenthesis_re.') # href = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # Title = $7 - \6 # matching quote - [ \n]* # ignore any spaces/tabs between closing quote and ) - )? # title is optional - \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes - ) - }xs', - array($this, '_doAnchors_inline_callback'), $text); - - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - \[ - ([^\[\]]+) # link text = $2; can\'t contain [ or ] - \] - ) - }xs', - array($this, '_doAnchors_reference_callback'), $text); - - $this->in_anchor = false; - return $text; - } - protected function _doAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id =& $matches[3]; - - if ($link_id == "") { - # for shortcut links like [this][] or [this]. - $link_id = $link_text; - } - - # lower-case and turn embedded newlines into spaces - $link_id = strtolower($link_id); - $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); - - if (isset($this->urls[$link_id])) { - $url = $this->urls[$link_id]; - $url = $this->encodeURLAttribute($url); - - $result = "titles[$link_id] ) ) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - if (isset($this->ref_attr[$link_id])) - $result .= $this->ref_attr[$link_id]; - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - $result = $this->hashPart($result); - } - else { - $result = $whole_match; - } - return $result; - } - protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); - - // if the URL was of the form it got caught by the HTML - // tag parser and hashed. Need to reverse the process before using the URL. - $unhashed = $this->unhash($url); - if ($unhashed != $url) - $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); - - $url = $this->encodeURLAttribute($url); - - $result = "encodeAttribute($title); - $result .= " title=\"$title\""; - } - $result .= $attr; - - $link_text = $this->runSpanGamut($link_text); - $result .= ">$link_text"; - - return $this->hashPart($result); - } - - - protected function doImages($text) { - # - # Turn Markdown image shortcuts into tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array($this, '_doImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.$this->nested_brackets_re.') # alt text = $2 - \] - \s? # One optional whitespace character - \( # literal paren - [ \n]* - (?: - <(\S*)> # src url = $3 - | - ('.$this->nested_url_parenthesis_re.') # src url = $4 - ) - [ \n]* - ( # $5 - ([\'"]) # quote char = $6 - (.*?) # title = $7 - \6 # matching quote - [ \n]* - )? # title is optional - \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes - ) - }xs', - array($this, '_doImages_inline_callback'), $text); - - return $text; - } - protected function _doImages_reference_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. - } - - $alt_text = $this->encodeAttribute($alt_text); - if (isset($this->urls[$link_id])) { - $url = $this->encodeURLAttribute($this->urls[$link_id]); - $result = "\"$alt_text\"";titles[$link_id])) { - $title = $this->titles[$link_id]; - $title = $this->encodeAttribute($title); - $result .= " title=\"$title\""; - } - if (isset($this->ref_attr[$link_id])) - $result .= $this->ref_attr[$link_id]; - $result .= $this->empty_element_suffix; - $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - protected function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; - $title =& $matches[7]; - $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); - - $alt_text = $this->encodeAttribute($alt_text); - $url = $this->encodeURLAttribute($url); - $result = "\"$alt_text\"";encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $attr; - $result .= $this->empty_element_suffix; - - return $this->hashPart($result); - } - - - protected function doHeaders($text) { - # - # Redefined to add id and class attribute support. - # - # Setext-style headers: - # Header 1 {#header1} - # ======== - # - # Header 2 {#header2 .class1 .class2} - # -------- - # - $text = preg_replace_callback( - '{ - (^.+?) # $1: Header text - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes - [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer - }mx', - array($this, '_doHeaders_callback_setext'), $text); - - # atx-style headers: - # # Header 1 {#header1} - # ## Header 2 {#header2} - # ## Header 2 with closing hashes ## {#header3.class1.class2} - # ... - # ###### Header 6 {.class2} - # - $text = preg_replace_callback('{ - ^(\#{1,6}) # $1 = string of #\'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #\'s (not counted) - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes - [ ]* - \n+ - }xm', - array($this, '_doHeaders_callback_atx'), $text); - - return $text; - } - protected function _doHeaders_callback_setext($matches) { - if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) - return $matches[0]; - $level = $matches[3]{0} == '=' ? 1 : 2; - $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]); - $block = "".$this->runSpanGamut($matches[1]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - protected function _doHeaders_callback_atx($matches) { - $level = strlen($matches[1]); - $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]); - $block = "".$this->runSpanGamut($matches[2]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - - - protected function doTables($text) { - # - # Form HTML tables. - # - $less_than_tab = $this->tab_width - 1; - # - # Find tables with leading pipe. - # - # | Header 1 | Header 2 - # | -------- | -------- - # | Cell 1 | Cell 2 - # | Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] # Optional leading pipe (present) - (.+) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - [ ]* # Allowed whitespace. - [|] .* \n # Row content. - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array($this, '_doTable_leadingPipe_callback'), $text); - - # - # Find tables without leading pipe. - # - # Header 1 | Header 2 - # -------- | -------- - # Cell 1 | Cell 2 - # Cell 3 | Cell 4 - # - $text = preg_replace_callback(' - { - ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - (\S.*[|].*) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. - ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline - - ( # $3: Cells - (?> - .* [|] .* \n # Row content - )* - ) - (?=\n|\Z) # Stop at final double newline. - }xm', - array($this, '_DoTable_callback'), $text); - - return $text; - } - protected function _doTable_leadingPipe_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove leading pipe for each row. - $content = preg_replace('/^ *[|]/m', '', $content); - - return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); - } - protected function _doTable_makeAlignAttr($alignname) - { - if (empty($this->table_align_class_tmpl)) - return " align=\"$alignname\""; - - $classname = str_replace('%%', $alignname, $this->table_align_class_tmpl); - return " class=\"$classname\""; - } - protected function _doTable_callback($matches) { - $head = $matches[1]; - $underline = $matches[2]; - $content = $matches[3]; - - # Remove any tailing pipes for each line. - $head = preg_replace('/[|] *$/m', '', $head); - $underline = preg_replace('/[|] *$/m', '', $underline); - $content = preg_replace('/[|] *$/m', '', $content); - - # Reading alignement from header underline. - $separators = preg_split('/ *[|] */', $underline); - foreach ($separators as $n => $s) { - if (preg_match('/^ *-+: *$/', $s)) - $attr[$n] = $this->_doTable_makeAlignAttr('right'); - else if (preg_match('/^ *:-+: *$/', $s)) - $attr[$n] = $this->_doTable_makeAlignAttr('center'); - else if (preg_match('/^ *:-+ *$/', $s)) - $attr[$n] = $this->_doTable_makeAlignAttr('left'); - else - $attr[$n] = ''; - } - - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $head = $this->parseSpan($head); - $headers = preg_split('/ *[|] */', $head); - $col_count = count($headers); - $attr = array_pad($attr, $col_count, ''); - - # Write column headers. - $text = "\n"; - $text .= "\n"; - $text .= "\n"; - foreach ($headers as $n => $header) - $text .= " ".$this->runSpanGamut(trim($header))."\n"; - $text .= "\n"; - $text .= "\n"; - - # Split content by row. - $rows = explode("\n", trim($content, "\n")); - - $text .= "\n"; - foreach ($rows as $row) { - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. - $row = $this->parseSpan($row); - - # Split row by cell. - $row_cells = preg_split('/ *[|] */', $row, $col_count); - $row_cells = array_pad($row_cells, $col_count, ''); - - $text .= "\n"; - foreach ($row_cells as $n => $cell) - $text .= " ".$this->runSpanGamut(trim($cell))."\n"; - $text .= "\n"; - } - $text .= "\n"; - $text .= "
    "; - - return $this->hashBlock($text) . "\n"; - } - - - protected function doDefLists($text) { - # - # Form HTML definition lists. - # - $less_than_tab = $this->tab_width - 1; - - # Re-usable pattern to match any entire dl list: - $whole_list_re = '(?> - ( # $1 = whole list - ( # $2 - [ ]{0,'.$less_than_tab.'} - ((?>.*\S.*\n)+) # $3 = defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another term - [ ]{0,'.$less_than_tab.'} - (?: \S.*\n )+? # defined term - \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - (?! # Negative lookahead for another definition - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition - ) - ) - ) - )'; // mx - - $text = preg_replace_callback('{ - (?>\A\n?|(?<=\n\n)) - '.$whole_list_re.' - }mx', - array($this, '_doDefLists_callback'), $text); - - return $text; - } - protected function _doDefLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: - $list = $matches[1]; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $result = trim($this->processDefListItems($list)); - $result = "
    \n" . $result . "\n
    "; - return $this->hashBlock($result) . "\n\n"; - } - - - protected function processDefListItems($list_str) { - # - # Process the contents of a single definition list, splitting it - # into individual term and definition list items. - # - $less_than_tab = $this->tab_width - 1; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - # Process definition terms. - $list_str = preg_replace_callback('{ - (?>\A\n?|\n\n+) # leading line - ( # definition terms = $1 - [ ]{0,'.$less_than_tab.'} # leading whitespace - (?!\:[ ]|[ ]) # negative lookahead for a definition - # mark (colon) or more whitespace. - (?> \S.* \n)+? # actual term (not whitespace). - ) - (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed - # with a definition mark. - }xm', - array($this, '_processDefListItems_callback_dt'), $list_str); - - # Process actual definitions. - $list_str = preg_replace_callback('{ - \n(\n+)? # leading line = $1 - ( # marker space = $2 - [ ]{0,'.$less_than_tab.'} # whitespace before colon - \:[ ]+ # definition mark (colon) - ) - ((?s:.+?)) # definition text = $3 - (?= \n+ # stop at next definition mark, - (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} \:[ ] | -
    | \z - ) - ) - }xm', - array($this, '_processDefListItems_callback_dd'), $list_str); - - return $list_str; - } - protected function _processDefListItems_callback_dt($matches) { - $terms = explode("\n", trim($matches[1])); - $text = ''; - foreach ($terms as $term) { - $term = $this->runSpanGamut(trim($term)); - $text .= "\n
    " . $term . "
    "; - } - return $text . "\n"; - } - protected function _processDefListItems_callback_dd($matches) { - $leading_line = $matches[1]; - $marker_space = $matches[2]; - $def = $matches[3]; - - if ($leading_line || preg_match('/\n{2,}/', $def)) { - # Replace marker with the appropriate whitespace indentation - $def = str_repeat(' ', strlen($marker_space)) . $def; - $def = $this->runBlockGamut($this->outdent($def . "\n\n")); - $def = "\n". $def ."\n"; - } - else { - $def = rtrim($def); - $def = $this->runSpanGamut($this->outdent($def)); - } - - return "\n
    " . $def . "
    \n"; - } - - - protected function doFencedCodeBlocks($text) { - # - # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # - $less_than_tab = $this->tab_width; - - $text = preg_replace_callback('{ - (?:\n|\A) - # 1: Opening marker - ( - (?:~{3,}|`{3,}) # 3 or more tildes/backticks. - ) - [ ]* - (?: - \.?([-_:a-zA-Z0-9]+) # 2: standalone class name - | - '.$this->id_class_attr_catch_re.' # 3: Extra attributes - )? - [ ]* \n # Whitespace and newline following marker. - - # 4: Content - ( - (?> - (?!\1 [ ]* \n) # Not a closing marker. - .*\n+ - )+ - ) - - # Closing marker. - \1 [ ]* (?= \n ) - }xm', - array($this, '_doFencedCodeBlocks_callback'), $text); - - return $text; - } - protected function _doFencedCodeBlocks_callback($matches) { - $classname =& $matches[2]; - $attrs =& $matches[3]; - $codeblock = $matches[4]; - $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - $codeblock = preg_replace_callback('/^\n+/', - array($this, '_doFencedCodeBlocks_newlines'), $codeblock); - - if ($classname != "") { - if ($classname{0} == '.') - $classname = substr($classname, 1); - $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; - } else { - $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs); - } - $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; - $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; - $codeblock = "$codeblock
    "; - - return "\n\n".$this->hashBlock($codeblock)."\n\n"; - } - protected function _doFencedCodeBlocks_newlines($matches) { - return str_repeat("empty_element_suffix", - strlen($matches[0])); - } - - - # - # Redefining emphasis markers so that emphasis by underscore does not - # work in the middle of a word. - # - protected $em_relist = array( - '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(? tags - # - # Strip leading and trailing lines: - $text = preg_replace('/\A\n+|\n+\z/', '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap

    tags and unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - $value = trim($this->runSpanGamut($value)); - - # Check if this should be enclosed in a paragraph. - # Clean tag hashes & block tag hashes are left alone. - $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); - - if ($is_p) { - $value = "

    $value

    "; - } - $grafs[$key] = $value; - } - - # Join grafs in one text, then unhash HTML tags. - $text = implode("\n\n", $grafs); - - # Finish by removing any tag hashes still present in $text. - $text = $this->unhash($text); - - return $text; - } - - - ### Footnotes - - protected function stripFootnotes($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [^id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 - [ ]* - \n? # maybe *one* newline - ( # text = $2 (no blank lines allowed) - (?: - .+ # actual text - | - \n # newlines but - (?!\[.+?\][ ]?:\s)# negative lookahead for footnote or link definition marker. - (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed - # by non-indented content - )* - ) - }xm', - array($this, '_stripFootnotes_callback'), - $text); - return $text; - } - protected function _stripFootnotes_callback($matches) { - $note_id = $this->fn_id_prefix . $matches[1]; - $this->footnotes[$note_id] = $this->outdent($matches[2]); - return ''; # String that will replace the block - } - - - protected function doFootnotes($text) { - # - # Replace footnote references in $text [^id] with a special text-token - # which will be replaced by the actual footnote marker in appendFootnotes. - # - if (!$this->in_anchor) { - $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); - } - return $text; - } - - - protected function appendFootnotes($text) { - # - # Append footnote list to text. - # - $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array($this, '_appendFootnotes_callback'), $text); - - if (!empty($this->footnotes_ordered)) { - $text .= "\n\n"; - $text .= "
    \n"; - $text .= "empty_element_suffix ."\n"; - $text .= "
      \n\n"; - - $attr = ""; - if ($this->fn_backlink_class != "") { - $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_backlink_title != "") { - $title = $this->fn_backlink_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - $num = 0; - - while (!empty($this->footnotes_ordered)) { - $footnote = reset($this->footnotes_ordered); - $note_id = key($this->footnotes_ordered); - unset($this->footnotes_ordered[$note_id]); - $ref_count = $this->footnotes_ref_count[$note_id]; - unset($this->footnotes_ref_count[$note_id]); - unset($this->footnotes[$note_id]); - - $footnote .= "\n"; # Need to append newline before parsing. - $footnote = $this->runBlockGamut("$footnote\n"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array($this, '_appendFootnotes_callback'), $footnote); - - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); - - # Prepare backlink, multiple backlinks if multiple references - $backlink = ""; - for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { - $backlink .= " "; - } - # Add backlink to last paragraph; create new paragraph if needed. - if (preg_match('{

      $}', $footnote)) { - $footnote = substr($footnote, 0, -4) . " $backlink

      "; - } else { - $footnote .= "\n\n

      $backlink

      "; - } - - $text .= "
    1. \n"; - $text .= $footnote . "\n"; - $text .= "
    2. \n\n"; - } - - $text .= "
    \n"; - $text .= "
    "; - } - return $text; - } - protected function _appendFootnotes_callback($matches) { - $node_id = $this->fn_id_prefix . $matches[1]; - - # Create footnote marker only if it has a corresponding footnote *and* - # the footnote hasn't been used by another marker. - if (isset($this->footnotes[$node_id])) { - $num =& $this->footnotes_numbers[$node_id]; - if (!isset($num)) { - # Transfer footnote content to the ordered list and give it its - # number - $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; - $this->footnotes_ref_count[$node_id] = 1; - $num = $this->footnote_counter++; - $ref_count_mark = ''; - } else { - $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1; - } - - $attr = ""; - if ($this->fn_link_class != "") { - $class = $this->fn_link_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; - } - if ($this->fn_link_title != "") { - $title = $this->fn_link_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - - $attr = str_replace("%%", $num, $attr); - $node_id = $this->encodeAttribute($node_id); - - return - "". - "$num". - ""; - } - - return "[^".$matches[1]."]"; - } - - - ### Abbreviations ### - - protected function stripAbbreviations($text) { - # - # Strips abbreviations from text, stores titles in hash references. - # - $less_than_tab = $this->tab_width - 1; - - # Link defs are in the form: [id]*: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 - (.*) # text = $2 (no blank lines allowed) - }xm', - array($this, '_stripAbbreviations_callback'), - $text); - return $text; - } - protected function _stripAbbreviations_callback($matches) { - $abbr_word = $matches[1]; - $abbr_desc = $matches[2]; - if ($this->abbr_word_re) - $this->abbr_word_re .= '|'; - $this->abbr_word_re .= preg_quote($abbr_word); - $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - return ''; # String that will replace the block - } - - - protected function doAbbreviations($text) { - # - # Find defined abbreviations in text and wrap them in elements. - # - if ($this->abbr_word_re) { - // cannot use the /x modifier because abbr_word_re may - // contain significant spaces: - $text = preg_replace_callback('{'. - '(?abbr_word_re.')'. - '(?![\w\x1A])'. - '}', - array($this, '_doAbbreviations_callback'), $text); - } - return $text; - } - protected function _doAbbreviations_callback($matches) { - $abbr = $matches[0]; - if (isset($this->abbr_desciptions[$abbr])) { - $desc = $this->abbr_desciptions[$abbr]; - if (empty($desc)) { - return $this->hashPart("$abbr"); - } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("$abbr"); - } - } else { - return $matches[0]; - } - } - -} diff --git a/sources/vendor/Michelf/MarkdownExtra.inc.php b/sources/vendor/Michelf/MarkdownExtra.inc.php deleted file mode 100644 index e11b1ef..0000000 --- a/sources/vendor/Michelf/MarkdownExtra.inc.php +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# -namespace Michelf; - - -# Just force Michelf/Markdown.php to load. This is needed to load -# the temporary implementation class. See below for details. -\Michelf\Markdown::MARKDOWNLIB_VERSION; - -# -# Markdown Extra Parser Class -# -# Note: Currently the implementation resides in the temporary class -# \Michelf\MarkdownExtra_TmpImpl (in the same file as \Michelf\Markdown). -# This makes it easier to propagate the changes between the three different -# packaging styles of PHP Markdown. Once this issue is resolved, the -# _MarkdownExtra_TmpImpl will disappear and this one will contain the code. -# - -class MarkdownExtra extends \Michelf\_MarkdownExtra_TmpImpl { - - ### Parser Implementation ### - - # Temporarily, the implemenation is in the _MarkdownExtra_TmpImpl class. - # See note above. - -} - diff --git a/sources/vendor/Michelf/MarkdownInterface.inc.php b/sources/vendor/Michelf/MarkdownInterface.inc.php deleted file mode 100644 index a023ed4..0000000 --- a/sources/vendor/Michelf/MarkdownInterface.inc.php +++ /dev/null @@ -1,9 +0,0 @@ - -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# -namespace Michelf; - - -# -# Markdown Parser Interface -# - -interface MarkdownInterface { - - # - # Initialize the parser and return the result of its transform method. - # This will work fine for derived classes too. - # - public static function defaultTransform($text); - - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # - public function transform($text); - -} diff --git a/sources/vendor/Parsedown/LICENSE.txt b/sources/vendor/Parsedown/LICENSE.txt new file mode 100644 index 0000000..baca86f --- /dev/null +++ b/sources/vendor/Parsedown/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Emanuil Rusev, erusev.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/sources/vendor/Parsedown/Parsedown.php b/sources/vendor/Parsedown/Parsedown.php new file mode 100644 index 0000000..5d6f0f7 --- /dev/null +++ b/sources/vendor/Parsedown/Parsedown.php @@ -0,0 +1,1423 @@ +Definitions = array(); + + # standardize line breaks + $text = str_replace("\r\n", "\n", $text); + $text = str_replace("\r", "\n", $text); + + # replace tabs with spaces + $text = str_replace("\t", ' ', $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + # + # Setters + # + + private $breaksEnabled; + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + private $markupEscaped; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Atx'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('Setext', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('Setext'), + '>' => array('Quote'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $DefinitionTypes = array( + '[' => array('Reference'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'CodeBlock', + ); + + # + # Blocks + # + + private function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['incomplete'])) + { + $Block = $this->{'addTo'.$CurrentBlock['type']}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if (method_exists($this, 'complete'.$CurrentBlock['type'])) + { + $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + } + + unset($CurrentBlock['incomplete']); + } + } + + # ~ + + $marker = $text[0]; + + if (isset($this->DefinitionTypes[$marker])) + { + foreach ($this->DefinitionTypes[$marker] as $definitionType) + { + $Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock); + + if (isset($Definition)) + { + $this->Definitions[$definitionType][$Definition['id']] = $Definition['data']; + + continue 2; + } + } + } + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{'identify'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + $Elements []= $CurrentBlock['element']; + + $Block['identified'] = true; + } + + if (method_exists($this, 'addTo'.$blockType)) + { + $Block['incomplete'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Elements []= $CurrentBlock['element']; + + $CurrentBlock = $this->buildParagraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['incomplete']) and method_exists($this, 'complete'.$CurrentBlock['type'])) + { + $CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock); + } + + # ~ + + $Elements []= $CurrentBlock['element']; + + unset($Elements[0]); + + # ~ + + $markup = $this->elements($Elements); + + # ~ + + return $markup; + } + + # + # Atx + + protected function identifyAtx($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # Code + + protected function identifyCodeBlock($Line) + { + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function addToCodeBlock($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function completeCodeBlock($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function identifyComment($Line) + { + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + { + $Block = array( + 'element' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function addToComment($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['element'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function identifyFencedCode($Line) + { + if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[2])) + { + $class = 'language-'.$matches[2]; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function addToFencedCode($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body'];; + + return $Block; + } + + protected function completeFencedCode($Block) + { + $text = $Block['element']['text']['text']; + + $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # List + + protected function identifyList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function addToList($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'[ ]+(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[1], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + # + # Quote + + protected function identifyQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function addToQuote($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function identifyRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function identifySetext($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function identifyMarkup($Line) + { + if ($this->markupEscaped) + { + return; + } + + if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>]*)?(\/?)[ ]*>/', $Line['text'], $matches)) + { + if (in_array($matches[1], $this->textLevelElements)) + { + return; + } + + $Block = array( + 'element' => $Line['body'], + ); + + if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text'])) + { + $Block['closed'] = true; + } + else + { + $Block['depth'] = 0; + $Block['name'] = $matches[1]; + } + + return $Block; + } + } + + protected function addToMarkup($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (preg_match('/<'.$Block['name'].'([ ][^\/]+)?>/', $Line['text'])) # opening tag + { + $Block['depth'] ++; + } + + if (stripos($Line['text'], '') !== false) # closing tag + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + $Block['element'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Table + + protected function identifyTable($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, -1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'align' => $alignment, + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function addToTable($Line, array $Block) + { + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + $cells = explode('|', $row); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'align' => $Block['alignments'][$index], + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # Definitions + # + + protected function identifyReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $Definition = array( + 'id' => strtolower($matches[1]), + 'data' => array( + 'url' => $matches[2], + ), + ); + + if (isset($matches[3])) + { + $Definition['data']['title'] = $matches[3]; + } + + return $Definition; + } + } + + # + # ~ + # + + protected function buildParagraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + # + # ~ + # + + protected function element(array $Element) + { + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + $markup .= ' '.$name.'="'.$value.'"'; + } + } + + if (isset($Element['text'])) + { + $markup .= '>'; + + if (isset($Element['handler'])) + { + $markup .= $this->$Element['handler']($Element['text']); + } + else + { + $markup .= $Element['text']; + } + + $markup .= ''; + } + else + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) + { + if ($Element === null) + { + continue; + } + + $markup .= "\n"; + + if (is_string($Element)) # because of Markup + { + $markup .= $Element; + + continue; + } + + $markup .= $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # + # Spans + # + + protected $SpanTypes = array( + '!' => array('Link'), # ? + '&' => array('Ampersand'), + '*' => array('Emphasis'), + '/' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Tag', 'LessThan'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('InlineCode'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $spanMarkerList = '*_!&[spanMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition += strpos($remainder, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->SpanTypes[$marker] as $spanType) + { + $handler = 'identify'.$spanType; + + $Span = $this->$handler($Excerpt); + + if ( ! isset($Span)) + { + continue; + } + + # The identified span can be ahead of the marker. + + if (isset($Span['position']) and $Span['position'] > $markerPosition) + { + continue; + } + + # Spans that start at the position of their marker don't have to set a position. + + if ( ! isset($Span['position'])) + { + $Span['position'] = $markerPosition; + } + + $plainText = substr($text, 0, $Span['position']); + + $markup .= $this->readPlainText($plainText); + + $markup .= isset($Span['markup']) ? $Span['markup'] : $this->element($Span['element']); + + $text = substr($text, $Span['position'] + $Span['extent']); + + $remainder = $text; + + $markerPosition = 0; + + continue 2; + } + + $remainder = substr($excerpt, 1); + + $markerPosition ++; + } + + $markup .= $this->readPlainText($text); + + return $markup; + } + + # + # ~ + # + + protected function identifyUrl($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[0][0]); + + return array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function identifyAmpersand($Excerpt) + { + if ( ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + } + + protected function identifyStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function identifyEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function identifyLessThan() + { + return array( + 'markup' => '<', + 'extent' => 1, + ); + } + + protected function identifyUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $Excerpt['text'], $matches)) + { + $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function identifyEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\S+?@\S+?)>/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => 'mailto:'.$matches[1], + ), + ), + ); + } + } + + protected function identifyTag($Excerpt) + { + if ($this->markupEscaped) + { + return; + } + + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<\/?\w.*?>/', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function identifyInlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function identifyLink($Excerpt) + { + $extent = $Excerpt['text'][0] === '!' ? 1 : 0; + + if (strpos($Excerpt['text'], ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $Excerpt['text'], $matches)) + { + $Link = array('text' => $matches[1], 'label' => strtolower($matches[1])); + + $extent += strlen($matches[0]); + + $substring = substr($Excerpt['text'], $extent); + + if (preg_match('/^\s*\[([^][]+)\]/', $substring, $matches)) + { + $Link['label'] = strtolower($matches[1]); + + if (isset($this->Definitions['Reference'][$Link['label']])) + { + $Link += $this->Definitions['Reference'][$Link['label']]; + + $extent += strlen($matches[0]); + } + else + { + return; + } + } + elseif (isset($this->Definitions['Reference'][$Link['label']])) + { + $Link += $this->Definitions['Reference'][$Link['label']]; + + if (preg_match('/^[ ]*\[\]/', $substring, $matches)) + { + $extent += strlen($matches[0]); + } + } + elseif (preg_match('/^\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $substring, $matches)) + { + $Link['url'] = $matches[1]; + + if (isset($matches[2])) + { + $Link['title'] = $matches[2]; + } + + $extent += strlen($matches[0]); + } + else + { + return; + } + } + else + { + return; + } + + $url = str_replace(array('&', '<'), array('&', '<'), $Link['url']); + + if ($Excerpt['text'][0] === '!') + { + $Element = array( + 'name' => 'img', + 'attributes' => array( + 'alt' => $Link['text'], + 'src' => $url, + ), + ); + } + else + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'text' => $Link['text'], + 'attributes' => array( + 'href' => $url, + ), + ); + } + + if (isset($Link['title'])) + { + $Element['attributes']['title'] = $Link['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function identifyEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + # + # ~ + + protected function readPlainText($text) + { + $breakMarker = $this->breaksEnabled ? "\n" : " \n"; + + $text = str_replace($breakMarker, "
    \n", $text); + + return $text; + } + + # + # ~ + # + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

    ') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

    "); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Multiton + # + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new self(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Deprecated Methods + # + + /** + * @deprecated in favor of "text" + */ + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + # + # Fields + # + + protected $Definitions; + + # + # Read-only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'sub', 'mark', + 'u', 'xm', 'sup', 'nobr', + 'var', 'ruby', + 'wbr', 'span', + 'time', + ); +} diff --git a/sources/vendor/PicoDb/Database.php b/sources/vendor/PicoDb/Database.php index 5d0beb8..c09d8a9 100644 --- a/sources/vendor/PicoDb/Database.php +++ b/sources/vendor/PicoDb/Database.php @@ -86,6 +86,11 @@ class Database public function escapeIdentifier($value) { + // Do not escape custom query + if (strpos($value, '.') !== false || strpos($value, ' ') !== false) { + return $value; + } + return $this->pdo->escapeIdentifier($value); } diff --git a/sources/vendor/PicoDb/Drivers/Mysql.php b/sources/vendor/PicoDb/Drivers/Mysql.php index 22277a0..96148a1 100644 --- a/sources/vendor/PicoDb/Drivers/Mysql.php +++ b/sources/vendor/PicoDb/Drivers/Mysql.php @@ -70,7 +70,6 @@ class Mysql extends \PDO { public function escapeIdentifier($value) { - if (strpos($value, '.') !== false) return $value; return '`'.$value.'`'; } } \ No newline at end of file diff --git a/sources/vendor/PicoDb/Drivers/Sqlite.php b/sources/vendor/PicoDb/Drivers/Sqlite.php index 83b61c4..38c823a 100644 --- a/sources/vendor/PicoDb/Drivers/Sqlite.php +++ b/sources/vendor/PicoDb/Drivers/Sqlite.php @@ -51,7 +51,6 @@ class Sqlite extends \PDO { public function escapeIdentifier($value) { - if (strpos($value, '.') !== false) return $value; return '"'.$value.'"'; } } \ No newline at end of file diff --git a/sources/vendor/PicoDb/Table.php b/sources/vendor/PicoDb/Table.php index cc63743..9c6bf4f 100644 --- a/sources/vendor/PicoDb/Table.php +++ b/sources/vendor/PicoDb/Table.php @@ -173,6 +173,10 @@ class Table public function buildSelectQuery() { + foreach ($this->columns as $key => $value) { + $this->columns[$key] = $this->db->escapeIdentifier($value); + } + return sprintf( 'SELECT %s %s FROM %s %s %s %s %s %s %s', $this->distinct ? 'DISTINCT' : '', @@ -350,7 +354,7 @@ class Table switch (strtolower($name)) { case 'in': - if (isset($arguments[1]) && is_array($arguments[1])) { + if (isset($arguments[1]) && is_array($arguments[1]) && ! empty($arguments[1])) { $sql = sprintf( '%s IN (%s)', @@ -361,7 +365,7 @@ class Table break; case 'notin': - if (isset($arguments[1]) && is_array($arguments[1])) { + if (isset($arguments[1]) && is_array($arguments[1]) && ! empty($arguments[1])) { $sql = sprintf( '%s NOT IN (%s)',