├── modules ├── tags │ ├── site.js │ ├── site.css │ ├── README.md │ └── modules.php ├── highlights │ ├── site.css │ └── site.js ├── profiles │ ├── site.css │ ├── js_modules │ │ └── route_handlers.js │ ├── README.md │ ├── functions.php │ └── site.js ├── dynamic_login │ ├── site.css │ ├── site.js │ ├── README.md │ └── setup.php ├── calendar │ ├── site.js │ ├── README.md │ ├── js_modules │ │ └── route_handlers.js │ ├── site.css │ └── setup.php ├── developer │ ├── site.js │ ├── site.css │ ├── README.md │ └── setup.php ├── history │ ├── site.js │ ├── site.css │ ├── js_modules │ │ └── route_handlers.js │ ├── README.md │ └── setup.php ├── ldap_contacts │ ├── site.css │ ├── site.js │ └── README.md ├── carddav_contacts │ ├── site.css │ ├── README.md │ └── site.js ├── feeds │ ├── site.css │ ├── js_modules │ │ └── route_handlers.js │ └── README.md ├── core │ ├── assets │ │ ├── images │ │ │ └── cloud.jpg │ │ └── fonts │ │ │ └── Behdad │ │ │ ├── Behdad-Regular.woff │ │ │ ├── Behdad-Regular.woff2 │ │ │ └── README.md │ ├── js_modules │ │ ├── utils │ │ │ ├── messageList.js │ │ │ ├── popovers.js │ │ │ └── loaders.js │ │ ├── actions │ │ │ ├── search.js │ │ │ ├── privacy_controls.js │ │ │ └── sortCombinedLists.js │ │ ├── markup │ │ │ └── pagination.js │ │ └── [cash] │ │ │ └── extend.js │ ├── modules.php │ ├── README.md │ └── navigation │ │ ├── utils.js │ │ └── navbar.js ├── advanced_search │ ├── README.md │ ├── site.css │ └── js_modules │ │ └── route_handlers.js ├── recaptcha │ ├── site.css │ ├── README.md │ └── setup.php ├── local_contacts │ ├── assets │ │ └── data │ │ │ └── contact_sample.csv │ ├── README.md │ └── setup.php ├── smtp │ ├── assets │ │ └── markdown │ │ │ └── fonts │ │ │ └── icomoon.woff │ ├── README.md │ └── js_modules │ │ └── [kindeditor] │ │ └── kindEditor.js ├── account │ ├── site.js │ ├── README.md │ └── site.css ├── saved_searches │ ├── README.md │ └── site.css ├── site │ ├── README.md │ ├── modules.php │ ├── site.js │ └── setup.php ├── themes │ ├── assets │ │ ├── default │ │ │ ├── fonts │ │ │ │ ├── roboto-condensed-v14-latin-regular.woff │ │ │ │ └── roboto-condensed-v14-latin-regular.woff2 │ │ │ └── css │ │ │ │ └── default.css │ │ ├── lux │ │ │ └── css │ │ │ │ └── lux.css │ │ ├── yeti │ │ │ └── css │ │ │ │ └── yeti.css │ │ ├── cosmo │ │ │ └── css │ │ │ │ └── cosmo.css │ │ ├── lumen │ │ │ └── css │ │ │ │ └── lumen.css │ │ ├── pulse │ │ │ └── css │ │ │ │ └── pulse.css │ │ ├── flatly │ │ │ └── css │ │ │ │ └── flatly.css │ │ ├── journal │ │ │ └── css │ │ │ │ └── journal.css │ │ ├── litera │ │ │ └── css │ │ │ │ └── litera.css │ │ ├── simplex │ │ │ └── css │ │ │ │ └── simplex.css │ │ ├── united │ │ │ └── css │ │ │ │ └── united.css │ │ ├── cerulean │ │ │ └── css │ │ │ │ └── cerulean.css │ │ ├── spacelab │ │ │ └── css │ │ │ │ └── spacelab.css │ │ ├── sandstone │ │ │ └── css │ │ │ │ └── sandstone.css │ │ ├── zephyr │ │ │ └── css │ │ │ │ └── zephyr.css │ │ ├── minty │ │ │ └── css │ │ │ │ └── minty.css │ │ ├── quartz │ │ │ └── css │ │ │ │ └── quartz.css │ │ ├── cyborg │ │ │ └── css │ │ │ │ └── cyborg.css │ │ ├── morph │ │ │ └── css │ │ │ │ └── morph.css │ │ ├── sketchy │ │ │ └── css │ │ │ │ └── sketchy.css │ │ ├── vapor │ │ │ └── css │ │ │ │ └── vapor.css │ │ ├── solar │ │ │ └── css │ │ │ │ └── solar.css │ │ ├── materia │ │ │ └── css │ │ │ │ └── materia.css │ │ ├── superhero │ │ │ └── css │ │ │ │ └── superhero.css │ │ ├── slate │ │ │ └── css │ │ │ │ └── slate.css │ │ └── darkly │ │ │ └── css │ │ │ └── darkly.css │ ├── README.md │ └── setup.php ├── keyboard_shortcuts │ ├── README.md │ ├── js_modules │ │ └── route_handlers.js │ └── site.css ├── hello_world │ ├── README.md │ ├── js_modules │ │ └── route_handlers.js │ ├── site.css │ └── site.js ├── imap │ ├── README.md │ ├── modules.php │ └── js_modules │ │ ├── utils │ │ ├── messageParts.js │ │ └── attachements.js │ │ └── route_handlers.js ├── wordpress │ ├── site.css │ └── README.md ├── recover_settings │ ├── site.css │ ├── README.md │ └── setup.php ├── inline_message │ ├── README.md │ ├── site.css │ └── setup.php ├── idle_timer │ ├── README.md │ ├── site.js │ └── setup.php ├── github │ ├── README.md │ └── site.css ├── imap_folders │ ├── README.md │ ├── js_modules │ │ └── route_handlers.js │ └── site.css ├── nasa │ ├── README.md │ ├── site.css │ ├── site.js │ └── setup.php ├── api_login │ ├── README.md │ └── setup.php ├── sievefilters │ ├── js_modules │ │ └── route_handlers.js │ ├── hm-sieve.php │ └── README.md ├── nux │ └── README.md ├── desktop_notifications │ ├── README.md │ ├── modules.php │ ├── setup.php │ └── site.js ├── 2fa │ ├── README.md │ └── setup.php ├── gmail_contacts │ ├── README.md │ └── setup.php ├── pgp │ ├── site.css │ ├── js_modules │ │ └── route_handlers.js │ ├── README.md │ └── setup.php └── contacts │ └── README.md ├── .dockerignore ├── tests ├── phpunit │ ├── data │ │ ├── foo.ini │ │ ├── app.php │ │ ├── schema.sql │ │ ├── schema_postgres.sql │ │ ├── schema_sqlite.sql │ │ ├── seed.sql │ │ ├── seed_mysql.sql │ │ └── seed_postgres.sql │ ├── elog.php │ ├── page_redirect.php │ ├── debug.php │ ├── handler_module_debug.php │ ├── redis_session.php │ ├── output.php │ ├── module_exec_debug.php │ ├── bootstrap.php │ ├── run.sh │ ├── oauth2.php │ ├── transform.php │ ├── request_key.php │ ├── environment.php │ ├── api.php │ ├── site_file_config.php │ ├── user_config_functions.php │ ├── output_module.php │ ├── tags.php │ └── crypt.php └── selenium │ ├── requirements.txt │ ├── runall.sh │ ├── get_config.php │ ├── local_creds.example.py │ ├── remote_creds.example.py │ ├── runner.py │ └── search.py ├── .travis ├── .my.cnf ├── run.sh ├── travis-ci-apache ├── dovecot.sh ├── creds.py-ie ├── creds.py-edge ├── creds.py-ff ├── creds.py-chrome └── creds.py-safari ├── .github ├── tests │ ├── my.cnf │ ├── scripts │ │ ├── postfix.sh │ │ └── dovecot.sh │ ├── selenium │ │ ├── webdriver │ │ │ └── webdriver.sh │ │ ├── nginx │ │ │ ├── php_fastcgi.conf │ │ │ └── nginx-site.conf │ │ └── creds.py │ └── test.sh ├── ISSUE_TEMPLATE │ ├── feature.md │ ├── devops.md │ ├── refactor.md │ ├── epic.md │ ├── release.md │ ├── question.md │ └── bug.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── Daily-Image.yml │ ├── Release-Image.yml │ └── release.yml ├── database ├── migrations │ ├── sqlite │ │ ├── 20241209010201_add_lock_columns.sql │ │ └── 20241209010200_add_hm_version_columns.sql │ ├── mysql │ │ └── 20241209010200_add_hm_version_columns.sql │ └── pgsql │ │ └── 20241209010200_add_hm_version_columns.sql ├── mysql_schema.sql ├── sqlite_schema.sql └── pgsql_schema.sql ├── third_party ├── kindeditor │ ├── themes │ │ ├── common │ │ │ ├── rm.gif │ │ │ ├── blank.gif │ │ │ ├── flash.gif │ │ │ ├── media.gif │ │ │ ├── anchor.gif │ │ │ └── loading.gif │ │ └── default │ │ │ ├── default.png │ │ │ └── background.png │ └── plugins │ │ ├── pagebreak │ │ └── pagebreak.js │ │ ├── preview │ │ └── preview.js │ │ ├── lineheight │ │ └── lineheight.js │ │ ├── autoheight │ │ └── autoheight.js │ │ ├── plainpaste │ │ └── plainpaste.js │ │ └── wordpaste │ │ └── wordpaste.js └── ays-beforeunload-shim.js ├── .coveralls.yml ├── RELEASE_NOTES ├── CONTRIBUTING.md ├── config ├── carddav.php ├── recaptcha.php ├── themes.php ├── wordpress.php ├── github.php └── 2fa.php ├── scripts ├── build_changelog.sh ├── release_changes.sh ├── setup_system.sh ├── load.php ├── create_account.php ├── delete_account.php └── update_password.php ├── lib ├── session_redis.php └── js_libs.php ├── assets └── data │ ├── server_accounts_sample.csv │ └── server_accounts_sample.yaml ├── docker ├── supervisord.conf ├── docker-compose.yaml ├── docker-entrypoint.sh └── nginx.conf ├── .gitignore ├── Makefile └── docker-compose.dev.yaml /modules/tags/site.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/highlights/site.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/profiles/site.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /data/ 2 | .git 3 | -------------------------------------------------------------------------------- /modules/dynamic_login/site.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/dynamic_login/site.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/phpunit/data/foo.ini: -------------------------------------------------------------------------------- 1 | foo=bar 2 | -------------------------------------------------------------------------------- /modules/calendar/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /modules/developer/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /modules/history/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /.travis/.my.cnf: -------------------------------------------------------------------------------- 1 | [mysql] 2 | socket = /var/run/mysqld/mysqld.sock 3 | -------------------------------------------------------------------------------- /modules/ldap_contacts/site.css: -------------------------------------------------------------------------------- 1 | .ldap_settings { display: none; } 2 | -------------------------------------------------------------------------------- /modules/carddav_contacts/site.css: -------------------------------------------------------------------------------- 1 | .carddav_settings { display: none; } 2 | -------------------------------------------------------------------------------- /modules/feeds/site.css: -------------------------------------------------------------------------------- 1 | .feeds_setting, .feeds_section { display: none; } 2 | -------------------------------------------------------------------------------- /.github/tests/my.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | user = "cypht_test" 3 | password = "cypht_test" 4 | host = "127.0.0.1" -------------------------------------------------------------------------------- /tests/phpunit/data/app.php: -------------------------------------------------------------------------------- 1 | 'bar','default_setting_foo' => 'bar',); 4 | -------------------------------------------------------------------------------- /modules/core/assets/images/cloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/core/assets/images/cloud.jpg -------------------------------------------------------------------------------- /modules/advanced_search/README.md: -------------------------------------------------------------------------------- 1 | ## Advanced Search 2 | 3 | Searches throughout various IMAP accounts in one interface. 4 | -------------------------------------------------------------------------------- /modules/recaptcha/site.css: -------------------------------------------------------------------------------- 1 | .g-recaptcha { margin-left: -12px; } 2 | .mobile .g-recaptcha { clear: left; margin-left: 20px; } 3 | -------------------------------------------------------------------------------- /database/migrations/sqlite/20241209010201_add_lock_columns.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE hm_user_session 2 | ADD COLUMN lock INT DEFAULT 0; 3 | -------------------------------------------------------------------------------- /database/migrations/mysql/20241209010200_add_hm_version_columns.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE hm_user_session ADD COLUMN hm_version INT DEFAULT 1; 2 | -------------------------------------------------------------------------------- /modules/local_contacts/assets/data/contact_sample.csv: -------------------------------------------------------------------------------- 1 | display_name,email_address,phone_number 2 | Thomas Tester,test@example.org,1234567890 -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/rm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/rm.gif -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | coverage_clover: tests/phpunit/clover.xml 3 | json_path: tests/phpunit/coveralls-upload.json 4 | -------------------------------------------------------------------------------- /database/migrations/pgsql/20241209010200_add_hm_version_columns.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE hm_user_session 2 | ADD COLUMN hm_version INT DEFAULT 1; 3 | -------------------------------------------------------------------------------- /database/migrations/sqlite/20241209010200_add_hm_version_columns.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE hm_user_session 2 | ADD COLUMN hm_version INT DEFAULT 1; 3 | -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/blank.gif -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/flash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/flash.gif -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/media.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/media.gif -------------------------------------------------------------------------------- /modules/smtp/assets/markdown/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/smtp/assets/markdown/fonts/icomoon.woff -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/anchor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/anchor.gif -------------------------------------------------------------------------------- /third_party/kindeditor/themes/common/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/common/loading.gif -------------------------------------------------------------------------------- /third_party/kindeditor/themes/default/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/default/default.png -------------------------------------------------------------------------------- /modules/account/site.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('.delete_user_form').on('submit', function() { 3 | return hm_delete_prompt(); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /modules/core/assets/fonts/Behdad/Behdad-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/core/assets/fonts/Behdad/Behdad-Regular.woff -------------------------------------------------------------------------------- /modules/core/assets/fonts/Behdad/Behdad-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/core/assets/fonts/Behdad/Behdad-Regular.woff2 -------------------------------------------------------------------------------- /modules/history/site.css: -------------------------------------------------------------------------------- 1 | .history_links { width: 70%; white-space: nowrap; padding: 20px; padding-left: 30px; } 2 | .history_links td { padding: 5px; } 3 | -------------------------------------------------------------------------------- /third_party/kindeditor/themes/default/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/third_party/kindeditor/themes/default/background.png -------------------------------------------------------------------------------- /modules/feeds/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyFeedMessageContentPageHandlers(routeParams) { 2 | feed_item_view(routeParams.uid, routeParams.list_path); 3 | } -------------------------------------------------------------------------------- /modules/profiles/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyProfilesPageHandler() { 2 | $('.add_profile').on("click", function() { $('.edit_profile').show(); }); 3 | } -------------------------------------------------------------------------------- /modules/saved_searches/README.md: -------------------------------------------------------------------------------- 1 | ## Saved searches 2 | 3 | This module set allows users to save the parameters of a search, and quickly 4 | access it again from the menu. 5 | -------------------------------------------------------------------------------- /modules/site/README.md: -------------------------------------------------------------------------------- 1 | ## Site 2 | 3 | This module set provides sites a way to add features to Cypht, or to override 4 | existing behavior without modifying any Cypht code. 5 | -------------------------------------------------------------------------------- /RELEASE_NOTES: -------------------------------------------------------------------------------- 1 | This is a placeholder for the release notes that will be updated each time we 2 | create a release. New development goes on in this branch, so be aware things 3 | may break! 4 | -------------------------------------------------------------------------------- /modules/recaptcha/README.md: -------------------------------------------------------------------------------- 1 | ## Recaptcha 2 | 3 | This module set enables Google Recaptcha support for the login page requiring 4 | users to "prove" they are not a bot when attempting to login. 5 | -------------------------------------------------------------------------------- /modules/themes/assets/default/fonts/roboto-condensed-v14-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/themes/assets/default/fonts/roboto-condensed-v14-latin-regular.woff -------------------------------------------------------------------------------- /modules/themes/assets/default/fonts/roboto-condensed-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Revisto/cypht/master/modules/themes/assets/default/fonts/roboto-condensed-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /modules/carddav_contacts/README.md: -------------------------------------------------------------------------------- 1 | ## CardDav module set 2 | 3 | Initial support for contacts from a CardDav server. Servers must be defined in the carddav.php file. As of right now support is read-only. 4 | -------------------------------------------------------------------------------- /modules/themes/README.md: -------------------------------------------------------------------------------- 1 | ## Themes 2 | 3 | This module set provides users the ability to select from different UI themes. 4 | A theme in Cypht is just a simple CSS file that overrides the default layout. 5 | -------------------------------------------------------------------------------- /modules/keyboard_shortcuts/README.md: -------------------------------------------------------------------------------- 1 | ## Keyboard shortcuts 2 | 3 | This module set adds the ability to use keyboard shortcuts to navigate Cypht, 4 | and adds a menu entry to the Settings menu so they can be customized. 5 | -------------------------------------------------------------------------------- /modules/keyboard_shortcuts/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyShortcutsPageHandlers() { 2 | $('.reset_shortcut').on("click", function() { 3 | Hm_Utils.redirect('?page=shortcuts'); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /modules/hello_world/README.md: -------------------------------------------------------------------------------- 1 | ## Hello world 2 | 3 | This module set is an example set to explain to developers how module 4 | sets work in Cypht. It has a lot of comments in the code to explain 5 | how module sets work. 6 | -------------------------------------------------------------------------------- /modules/history/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyHistoryPageHandlers() { 2 | // When Message list style setting is set to news 3 | $('.news_cell').removeClass('checkbox_cell'); 4 | $('.news_cell').attr('colspan', 5); 5 | } -------------------------------------------------------------------------------- /modules/imap/README.md: -------------------------------------------------------------------------------- 1 | ## IMAP 2 | 3 | This module set allows you to read and manage messages in E-mail accounts using 4 | the IMAP protocol. If you are using Cypht as a webmail program, you definitely 5 | want this module set enabled. 6 | -------------------------------------------------------------------------------- /modules/wordpress/site.css: -------------------------------------------------------------------------------- 1 | .wordpress_connect_section { padding: 20px; padding-bottom: 40px; display: none; padding-left: 40px; } 2 | .wp_disconnect { margin-left: 20px; margin-top: 20px; } 3 | .wp_notifications_setting { display: none; } 4 | -------------------------------------------------------------------------------- /modules/recover_settings/site.css: -------------------------------------------------------------------------------- 1 | .menu_recover_settings .unread_link { font-weight: bold; } 2 | .recover_form { max-width: 600px; margin-left: 30px; margin-top: 20px; max-width: 60%; color: #666; } 3 | .recover_form input { margin-top: 20px; } 4 | -------------------------------------------------------------------------------- /modules/ldap_contacts/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function() { 4 | $('.ldap_password_change').on("click", function() { 5 | $(this).prev().prop('disabled', false); 6 | $(this).prev().attr('placeholder', ''); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /.github/tests/scripts/postfix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo systemctl stop postfix.service 4 | sudo sed -i 's/#myorigin/myorigin/g' /etc/postfix/main.cf 5 | sudo -H postconf virtual_transport=lmtp:unix:private/dovecot-lmtp 6 | sudo systemctl start postfix.service -------------------------------------------------------------------------------- /modules/calendar/README.md: -------------------------------------------------------------------------------- 1 | ## Calendar 2 | 3 | This module set provides a basic calendar, with limited event support. Some 4 | work has gone into providing integration with CalDav/Ical formats, however 5 | there is a lot of work left to go for that. 6 | 7 | -------------------------------------------------------------------------------- /modules/carddav_contacts/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function() { 4 | $('.carddav_password_change').on("click", function() { 5 | $(this).prev().prop('disabled', false); 6 | $(this).prev().attr('placeholder', ''); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /modules/inline_message/README.md: -------------------------------------------------------------------------------- 1 | ## Inline message 2 | 3 | This module set provides options to users to view message content inline in the 4 | message list instead of opening a new window. This behavior can be controlled 5 | from the Settings->Site page. 6 | -------------------------------------------------------------------------------- /modules/local_contacts/README.md: -------------------------------------------------------------------------------- 1 | ## Local contacts 2 | 3 | This module set provides built in (and very basic) contact support in Cypht. 4 | Like all other contact sources (gmail, ldap), the generic contacts module set 5 | must also be enabled for this to work. 6 | -------------------------------------------------------------------------------- /modules/idle_timer/README.md: -------------------------------------------------------------------------------- 1 | ## Idle timer 2 | 3 | This module set allows user to designate an idle timeout that will cause them 4 | to be logged out of Cypht once it expires. It is not compatible with the api 5 | login module set, so make sure to disable it if using that. 6 | -------------------------------------------------------------------------------- /modules/tags/site.css: -------------------------------------------------------------------------------- 1 | .tag_icon { position: absolute; right: 50px; top: 18px; } 2 | .mobile .tag_icon { display: none; } 3 | .tags_action_btn { width: 30px !important; height: 30px !important; } 4 | .mr-4 { margin-right: 4px !important; } 5 | .tag_setting { display: none; } 6 | -------------------------------------------------------------------------------- /modules/core/assets/fonts/Behdad/README.md: -------------------------------------------------------------------------------- 1 | Thanks to [font-store](https://github.com/font-store) and the designer [Mohammad Saleh Souzanchi](http://github.com/zoghal) who made this awesome Persian/Arabic Open Source font which is [Behdad](https://github.com/font-store/BehdadFont). -------------------------------------------------------------------------------- /modules/github/README.md: -------------------------------------------------------------------------------- 1 | ## Github 2 | 3 | This module set adds Github notification support to Cypht. It provides the 4 | ability to add repositories to monitor in the Settings->Servers page, adds 5 | a section to the menu to view them, and integrates with the combined views. 6 | -------------------------------------------------------------------------------- /modules/imap_folders/README.md: -------------------------------------------------------------------------------- 1 | ## IMAP Folders 2 | 3 | This module set provides users with the ability to add/rename/delete folders 4 | in there IMAP accounts. It requires the imap module set to work. It also supports 5 | assigning special folders to an account (Sent/Trash). 6 | -------------------------------------------------------------------------------- /modules/smtp/README.md: -------------------------------------------------------------------------------- 1 | ## SMTP 2 | 3 | This module set provides support for sending E-mail using Cypht. It supports 4 | plain text, HTML, and markdown formatted messages. It adds a section to the 5 | Settings->Servers page for users to add as many SMTP servers as they want. 6 | -------------------------------------------------------------------------------- /modules/hello_world/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * If we are on the "hello_world" page, activate the click handler 3 | */ 4 | function applyHelloWorldPageHandlers() { 5 | $('.hw_ajax_link').on("click", function() { 6 | hello_world_ajax_update(); 7 | }); 8 | } -------------------------------------------------------------------------------- /modules/ldap_contacts/README.md: -------------------------------------------------------------------------------- 1 | ## LDAP Contacts 2 | 3 | This module set adds support for using one or more LDAP servers to store 4 | contacts. Contact sources can be read/write or read only. Sites must 5 | enable the contacts module set and setup the ldap.php file for it to work. 6 | -------------------------------------------------------------------------------- /modules/nasa/README.md: -------------------------------------------------------------------------------- 1 | ## NASA 2 | 3 | This module set uses the NASA API to provide the APOD (Astronomy Picture Of the 4 | Day) inside Cypht. It adds a menu entry and a page that display the image and 5 | description, and a dialog in the Settings->Server page to configure that API. 6 | -------------------------------------------------------------------------------- /modules/api_login/README.md: -------------------------------------------------------------------------------- 1 | ## API Login 2 | 3 | This module set helps make it easier to integrate Cypht with other web 4 | applications making "single sign on" possible. More information about 5 | how this works can be found here: 6 | 7 | https://github.com/cypht-org/cypht/wiki/API-Login 8 | -------------------------------------------------------------------------------- /modules/wordpress/README.md: -------------------------------------------------------------------------------- 1 | ## Wordpress 2 | 3 | This module set uses the WordPress.com API to integrate WordPress notices into 4 | Cypht. It adds a WordPress section to the menu that displays notifications, and 5 | a dialog to the Settings->Site page to setup the connection to WordPress. 6 | -------------------------------------------------------------------------------- /tests/selenium/requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==23.1.0 2 | certifi==2024.7.4 3 | h11==0.16.0 4 | idna==3.7 5 | outcome==1.2.0 6 | PySocks==1.7.1 7 | selenium==4.14.0 8 | sniffio==1.3.0 9 | sortedcontainers==2.4.0 10 | trio==0.22.2 11 | trio-websocket==0.11.1 12 | urllib3==2.5.0 13 | wsproto==1.2.0 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We appreciate all feedback and support of the project. 2 | Please use the issue tracker at GitHub to submit bug reports or feature requests: 3 | 4 | https://github.com/cypht-org/cypht/issues 5 | 6 | If you have questions, please join our chat at: https://gitter.im/cypht-org/community 7 | -------------------------------------------------------------------------------- /modules/sievefilters/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyBlockListPageHandlers() { 2 | blockListPageHandlers(); 3 | } 4 | 5 | function applySieveFiltersPageHandler() { 6 | sieveFiltersPageHandler(); 7 | 8 | return () => { 9 | cleanUpSieveFiltersPage(); 10 | }; 11 | } -------------------------------------------------------------------------------- /modules/core/js_modules/utils/messageList.js: -------------------------------------------------------------------------------- 1 | function triggerNewMessageEvent(uid, row) { 2 | const newRowEvent = new CustomEvent('new-message', { 3 | detail: { 4 | uid: uid, 5 | row: row, 6 | } 7 | }); 8 | window.dispatchEvent(newRowEvent); 9 | } 10 | -------------------------------------------------------------------------------- /modules/history/README.md: -------------------------------------------------------------------------------- 1 | ## History 2 | 3 | This module set provides a history of all the content a user has access since 4 | they last logged in to Cypht. The history itself is entirely saved in Javascript, 5 | and only records content views like reading an E-mail message, github notice, feed 6 | item, etc. 7 | -------------------------------------------------------------------------------- /modules/nux/README.md: -------------------------------------------------------------------------------- 1 | ## NUX 2 | 3 | NUX is short for "New User Experience" and is a module set intented to make 4 | using Cypht easier for new users. It adds a help dialog to the home page (which 5 | needs content), and a wizard to walk users through adding new E-mail accounts 6 | to the Settings->Server page. 7 | -------------------------------------------------------------------------------- /modules/dynamic_login/README.md: -------------------------------------------------------------------------------- 1 | ## Dynamic login 2 | 3 | This module set enables a special login flow allowing you to select 4 | at login the E-mail service you want to authenticate with. It supports 5 | many existing E-mail service providers, and has a custom domain option 6 | that attempts to auto-discover how to login. 7 | -------------------------------------------------------------------------------- /modules/core/js_modules/utils/popovers.js: -------------------------------------------------------------------------------- 1 | function sessionAvailableOnlyActionInfo(element) { 2 | return new bootstrap.Popover(element, { 3 | title: 'Session-limited action', 4 | content: 'Note that the action will persist only during the current session, unless the settings are saved.', 5 | trigger: 'hover', 6 | }); 7 | } -------------------------------------------------------------------------------- /config/carddav.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'server' => env('CARD_DAV_SERVER', 'http://localhost:5232'), 13 | ] 14 | ]; 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature 3 | about: Suggest an idea for this project 4 | labels: feature 5 | title: 🚀 [Feature] 6 | --- 7 | 8 | 9 | ## 🚀 Feature 10 | 11 | -------------------------------------------------------------------------------- /.github/tests/scripts/dovecot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | systemctl stop dovecot.service 4 | sudo echo "disable_plaintext_auth = no" >> /etc/dovecot/conf.d/10-auth.conf 5 | sudo sed -i "s/auth_mechanisms = plain/auth_mechanisms = plain login/g" /etc/dovecot/conf.d/10-auth.conf 6 | sudo sed -i "s/ssl = yes/ssl = no/g" /etc/dovecot/conf.d/10-ssl.conf 7 | systemctl start dovecot.service -------------------------------------------------------------------------------- /modules/account/README.md: -------------------------------------------------------------------------------- 1 | ## Account 2 | 3 | This module set provides some basic user management and password changing 4 | capabilities inside of Cypht. It only works for sites using the DB 5 | authentication type. When enabled, it allows users to change there password 6 | from the Settings menu, and allows sites to designate admins that can create or 7 | delete user accounts. 8 | -------------------------------------------------------------------------------- /modules/feeds/README.md: -------------------------------------------------------------------------------- 1 | ## Feeds 2 | 3 | This module set adds RSS/ATOM feed reading support to Cypht. It adds a new 4 | section to the menu for configured feeds, allows them to be integrated into the 5 | combined views, and provides add/remove feed options to the Settings->Servers 6 | page. Like E-mail content, feeds are filtered for security removing any remote 7 | resources. 8 | -------------------------------------------------------------------------------- /modules/desktop_notifications/README.md: -------------------------------------------------------------------------------- 1 | ## Desktop Notifications 2 | 3 | This module set enables desktop notifications for newly arrived content 4 | to the Unread page. It uses the notifications API in JavaScript. 5 | 6 | https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API 7 | 8 | Users must opt-in in there browser to receive notifications. 9 | -------------------------------------------------------------------------------- /tests/selenium/runall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYTHON=$(command -v python3) 4 | rm -rf __pycache__/ 5 | 6 | for suite in login.py folder_list.py pages.py profiles.py settings.py servers.py send.py search.py inline_msg.py keyboard_shortcuts.py 7 | do 8 | export TEST_SUITE="$suite" 9 | "$PYTHON" -u ./$suite 10 | if [ $? -ne 0 ]; then 11 | exit 1 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /modules/tags/README.md: -------------------------------------------------------------------------------- 1 | ## Tags 2 | 3 | This module allows users to organize content effectively by utilizing tags/labels. It is designed to enhance content management by enabling users to assign relevant tags, facilitating easy categorization, filtering, and retrieval of content. The tagging feature is fully implemented, allowing for seamless interaction where users can add, edit, or remove tags as needed. 4 | -------------------------------------------------------------------------------- /scripts/build_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Should we continue to use this? 4 | 5 | 6 | CYPHT_DIR="/home/jason/cypht" 7 | 8 | cd "$CYPHT_DIR" 9 | git log --pretty=format:'%h% - %s [%aD]' \ 10 | --abbrev-commit \ 11 | --since 2017-05-11 \ 12 | --graph release-1.1.0 \ 13 | | sed -r "s/[|\*\/\\]//g" \ 14 | | tr -s ' ' \ 15 | | grep -v '^ $' \ 16 | | sed -r "s/^ //" > CHANGES 17 | -------------------------------------------------------------------------------- /modules/recover_settings/README.md: -------------------------------------------------------------------------------- 1 | ## Recover settings 2 | 3 | This module set provides users the ability to recover settings after a password 4 | change. Cypht encrypts user settings with the user password as the basis for 5 | the encryption key, so if that is changed externally, the user settings cannot 6 | be decrypted. Using this module set, users can enter there old and new 7 | passwords, and the settings can be converted. 8 | -------------------------------------------------------------------------------- /modules/2fa/README.md: -------------------------------------------------------------------------------- 1 | ## 2FA 2 | 3 | This module set provides the ability for users to enable "two factor 4 | authentication" (2fa), requiring a user to enter a one time code from an 5 | authentication app (like Google Authenticator). It uses the TOTP protocol and 6 | provides a QR code image in the settings page to make it easy to setup in 7 | authentication apps. It also provides backup code support in case of account 8 | lock out. 9 | -------------------------------------------------------------------------------- /scripts/release_changes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Should we continue to use this? 4 | 5 | CYPHT_DIR="/home/jason/cypht" 6 | VERSION="v1.1.0-rc4" 7 | SINCE="2017-05-11" 8 | 9 | cd "$CYPHT_DIR" 10 | git log --pretty=format:'%h% - %s [%aD]' \ 11 | --abbrev-commit \ 12 | --since "$SINCE" \ 13 | "$VERSION"..HEAD \ 14 | | sed -r "s/[|\*\/\\]//g" \ 15 | | tr -s ' ' \ 16 | | grep -v '^ $' \ 17 | | sed -r "s/^ //" 18 | -------------------------------------------------------------------------------- /.github/tests/selenium/webdriver/webdriver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$( 4 | dpkg -s google-chrome-stable | grep Version | awk '{print $2}' | sed 's/-.*//' 5 | ) 6 | 7 | wget -O /tmp/chromedriver-linux64.zip https://storage.googleapis.com/chrome-for-testing-public/"${VERSION}"/linux64/chromedriver-linux64.zip 8 | 9 | unzip /tmp/chromedriver-linux64.zip -d /tmp 10 | 11 | mv /tmp/chromedriver-linux64/chromedriver /usr/bin/chromedriver 12 | -------------------------------------------------------------------------------- /modules/gmail_contacts/README.md: -------------------------------------------------------------------------------- 1 | ## Gmail contacts 2 | 3 | This module set provides read-only access to Gmail contacts associated 4 | with any Gmail account setup in Cypht. Gmail accounts must be enabled 5 | using the "add new account" feature of the nux module set and must use 6 | the Oauth2 authentication methods, since that is the only way we can 7 | access the contacts API. 8 | 9 | Related: https://www.cypht.org/cypht-enable-gmail-oauth/ 10 | -------------------------------------------------------------------------------- /.travis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpunit_tests() { 4 | cd tests/phpunit/ && /usr/local/bin/phpunit && cd ../../ 5 | } 6 | 7 | selenium_tests() { 8 | cd tests/selenium/ && sh ./runall.sh && cd ../../ 9 | } 10 | 11 | BUILD="$DB$TRAVIS_PHP_VERSION" 12 | case "$BUILD" in 13 | postgresql8.1) 14 | #phpunit_tests && selenium_tests 15 | phpunit_tests 16 | ;; 17 | *) 18 | phpunit_tests 19 | ;; 20 | esac 21 | -------------------------------------------------------------------------------- /modules/github/site.css: -------------------------------------------------------------------------------- 1 | .github_connect_section { padding: 20px; padding-bottom: 40px; display: none; padding-left: 40px; } 2 | .github_connect_section .add_server { width: 180px; } 3 | .github_connect_section .server_title { margin-bottom: 10px; } 4 | .github_link { margin-left: 10px; display: inline; } 5 | .github_all_setting { display: none; } 6 | .github_para { white-space: pre; border-bottom: solid 1px #ccc; padding-bottom: 20px; margin-bottom: 20px; } 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/devops.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💥 DevOp 3 | about: Help us manage our deployed Software. 4 | labels: devops 5 | title: 💥 [DevOps] 6 | --- 7 | 8 | 9 | ## 💥 DevOps 10 | 11 | -------------------------------------------------------------------------------- /modules/account/site.css: -------------------------------------------------------------------------------- 1 | .create_user { margin-top: 20px; margin-left: 10px; } 2 | .create_user input { clear: both; float: left; padding: 4px; margin-left: 20px; margin-top: 10px; margin-bottom: 10px; } 3 | .create_account_link { margin-left: 120px; float: left; clear: left; font-size: 115%; padding: 5px; } 4 | .user_list { padding-left: 40px; padding-top: 20px; } 5 | .user_list td {padding-bottom: 10px; } 6 | .user_list .user_delete { cursor: pointer; margin-right: 10px; } 7 | -------------------------------------------------------------------------------- /modules/core/js_modules/actions/search.js: -------------------------------------------------------------------------------- 1 | function performSearch(routeParams) { 2 | if (routeParams.search_terms) { 3 | const messages = new Hm_MessagesStore('search', Hm_Utils.get_url_page_number(), `${routeParams.search_terms}_${routeParams.search_fld}_${routeParams.search_since}`, routeParams.sort); 4 | messages.load(true, false, false, function() { 5 | display_imap_mailbox(messages.rows, messages.list, messages); 6 | }); 7 | } 8 | } -------------------------------------------------------------------------------- /modules/imap/modules.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## 🔧 Refactor 10 | 11 | -------------------------------------------------------------------------------- /modules/desktop_notifications/modules.php: -------------------------------------------------------------------------------- 1 | '; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/highlights/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function() { 4 | $('.rule_del').on('click', function() { 5 | return hm_delete_prompt(); 6 | }); 7 | $('.hl_source_type').on('change', function() { 8 | $('.imap_row').addClass('d-none'); 9 | $('.github_row').addClass('d-none'); 10 | $('.feeds_row').addClass('d-none'); 11 | var selected = $(this).val(); 12 | $('.'+selected+'_row').removeClass('d-none'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🌟 Epic 3 | about: Define a big development Step 4 | labels: epic 5 | title: 🌟 [EPIC] 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | ## 🌟 EPIC 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎂 Release 3 | about: Define a Release 4 | labels: release 5 | title: 🎂 [RELEASE] 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | ## 🎂 RELEASE 13 | 14 | -------------------------------------------------------------------------------- /modules/hello_world/site.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Style defined here will be combined and optionally minified with css from other 3 | * modules duing the build process. Only one css file is used in normal mode. All 4 | * individual css files from all activated modules are used in DEBUG_MODE 5 | */ 6 | .hw { float: right; margin-right: 200px; margin-top: 5px; } 7 | .hwpage { width: 100%; font-size: 80pt; text-align: center; margin-top: 100px; color: #aaa; } 8 | .hw_ajax_link { font-size: 12pt; cursor: pointer; } 9 | -------------------------------------------------------------------------------- /tests/phpunit/elog.php: -------------------------------------------------------------------------------- 1 | assertEquals('string: test', elog('test')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /database/mysql_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS hm_user ( 2 | username VARCHAR(255), 3 | hash VARCHAR(255), 4 | PRIMARY KEY (username) 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS hm_user_session ( 8 | hm_id VARCHAR(255), 9 | data LONGBLOB, 10 | date TIMESTAMP, 11 | hm_version INT DEFAULT 1, 12 | PRIMARY KEY (hm_id) 13 | ); 14 | 15 | CREATE TABLE IF NOT EXISTS hm_user_settings ( 16 | username VARCHAR(255), 17 | settings LONGBLOB, 18 | PRIMARY KEY (username) 19 | ); -------------------------------------------------------------------------------- /modules/desktop_notifications/setup.php: -------------------------------------------------------------------------------- 1 | array( 12 | ), 13 | 'allowed_output' => array( 14 | ), 15 | 'allowed_get' => array( 16 | ), 17 | 'allowed_post' => array( 18 | ) 19 | ); 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | ## 💬 Issue 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /modules/profiles/README.md: -------------------------------------------------------------------------------- 1 | ## Profiles 2 | 3 | This module set adds a Profile page to the Settings menu that allows users to 4 | configure profiles. A profile ties together an IMAP server, an SMTP server, and 5 | values for the users E-mail address, reply to address, display name, and 6 | signature. A profile is required for Cypht to be able to save an outbound 7 | message into an IMAP server Sent folder. When composing a message users can 8 | select which profile to use, and when replying the profile is automatically 9 | selected. 10 | -------------------------------------------------------------------------------- /tests/selenium/get_config.php: -------------------------------------------------------------------------------- 1 | load(); 9 | 10 | /* get config object */ 11 | $config = new Hm_Site_Config_File(); 12 | /* set the default since and per_source values */ 13 | $environment->define_default_constants($config); 14 | $config = merge_config_files('../../config'); 15 | 16 | echo json_encode($config); 17 | -------------------------------------------------------------------------------- /modules/calendar/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyCalendarPageHandlers() { 2 | $('.event_delete').on("click", function() { 3 | if (hm_delete_prompt()) { 4 | $(this).parent().submit(); 5 | } 6 | }); 7 | $('.cal_title').on("click", function(e) { 8 | e.preventDefault(); 9 | $('.event_details').hide(); 10 | $('.event_details', $(this).parent()).show(); 11 | $('.event_details').on("click", function() { 12 | $(this).hide(); 13 | }); 14 | }); 15 | } -------------------------------------------------------------------------------- /modules/developer/site.css: -------------------------------------------------------------------------------- 1 | .hmod_val, .omod_val, .hmod, .omod { color: #666; display: none; padding: 4px; padding-left: 60px; } 2 | .config_map_page { color: #666; font-size: 110%; cursor: pointer; padding-top: 20px; padding-left: 40px; } 3 | .config_map { margin-left: 40px; min-width: 400px; } 4 | .config_map th {border-bottom: solid 1px #eee; padding-left: 20px; display: none; padding-top: 10px; color: #666; font-size: 90%; text-align: left; } 5 | .config_map th { padding-left: 60px; } 6 | .config_map img { width: 16px; height: 16px; opacity: .6 } 7 | -------------------------------------------------------------------------------- /modules/saved_searches/site.css: -------------------------------------------------------------------------------- 1 | .saved_searches_form { margin-left: 30px; } 2 | .saved_searches_form input { margin-left: 3px; margin-right: 3px; } 3 | .update_search_label img { opacity: .5; position: absolute; right: 105px; top: 21px; } 4 | .update_saved_search_title { color: #666; font-size: 110%; padding: 5px; margin-bottom: 10px; } 5 | .update_search img { opacity: .5; position: absolute; right: 135px; top: 22px; } 6 | 7 | .mobile .save_search, .mobile .update_search_label, .mobile .delete_search, .mobile .add_search { display: none !important; } 8 | -------------------------------------------------------------------------------- /modules/tags/modules.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | ## 💬 Question 13 | 14 | -------------------------------------------------------------------------------- /modules/dynamic_login/setup.php: -------------------------------------------------------------------------------- 1 | array(), 13 | 'allowed_cookie' => array(), 14 | 'allowed_server' => array(), 15 | 'allowed_get' => array(), 16 | 'allowed_post' => array('email_provider' => FILTER_UNSAFE_RAW) 17 | ); 18 | -------------------------------------------------------------------------------- /tests/phpunit/data/schema_sqlite.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS hm_user; 2 | 3 | DROP TABLE IF EXISTS hm_user_session; 4 | 5 | DROP TABLE IF EXISTS hm_user_settings; 6 | 7 | CREATE TABLE IF NOT EXISTS hm_user (username varchar(255), hash varchar(255), primary key (username)); 8 | 9 | CREATE TABLE IF NOT EXISTS hm_user_session (hm_id varchar(255), data longblob, date timestamp, lock int default 0, hm_version int default 1, primary key (hm_id)); 10 | 11 | CREATE TABLE IF NOT EXISTS hm_user_settings(username varchar(255), settings longblob, primary key (username)); 12 | -------------------------------------------------------------------------------- /.github/tests/selenium/nginx/php_fastcgi.conf: -------------------------------------------------------------------------------- 1 | # 404 2 | try_files $fastcgi_script_name =404; 3 | 4 | # default fastcgi_params 5 | include fastcgi_params; 6 | 7 | # fastcgi settings 8 | fastcgi_index index.php; 9 | fastcgi_buffers 8 16k; 10 | fastcgi_buffer_size 32k; 11 | 12 | # fastcgi params 13 | fastcgi_param DOCUMENT_ROOT $realpath_root; 14 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 15 | fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/"; -------------------------------------------------------------------------------- /modules/pgp/site.css: -------------------------------------------------------------------------------- 1 | .pgp_block { margin-bottom: 30px; margin-left: 40px; padding-top: 20px; } 2 | .priv_keys, .public_keys { display: none; } 3 | .private_key_count, .key_count { float: right; margin-right: 120px; font-size: 90%; } 4 | .pgp_msg_controls { display: none; position: absolute; right: 20px; top: 70px; } 5 | .pgp_sign { display: none; } 6 | .passphrase_prompt { display: none; text-align: center; top: 0px; padding: 20px; background-color: #fff; position: absolute; z-index: 101; left: 0; right: 0; margin: auto; width: 300px; border: solid 1px #ccc; border-top: none; } 7 | -------------------------------------------------------------------------------- /database/sqlite_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS hm_user ( 2 | username TEXT NOT NULL, 3 | hash TEXT NOT NULL, 4 | PRIMARY KEY (username) 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS hm_user_session ( 8 | hm_id TEXT NOT NULL, 9 | data BLOB, 10 | date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 11 | hm_version INT DEFAULT 1, 12 | lock INT DEFAULT 0, 13 | PRIMARY KEY (hm_id) 14 | ); 15 | 16 | CREATE TABLE IF NOT EXISTS hm_user_settings ( 17 | username TEXT NOT NULL, 18 | settings BLOB, 19 | PRIMARY KEY (username) 20 | ); 21 | -------------------------------------------------------------------------------- /config/recaptcha.php: -------------------------------------------------------------------------------- 1 | [ 13 | /* Client secret for the recaptcha admin */ 14 | 'secret' => env('RECAPTCHA_SECRET', ''), 15 | 16 | /* Site key from the recaptcha admin */ 17 | 'site_key' => env('RECAPTCHA_SITE_KEY', '') 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /modules/recaptcha/setup.php: -------------------------------------------------------------------------------- 1 | array( 13 | 'g-recaptcha-response' => FILTER_UNSAFE_RAW 14 | ) 15 | ); 16 | -------------------------------------------------------------------------------- /.github/tests/selenium/nginx/nginx-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name cypht-test.org; 5 | set $base /var/www/cypht; 6 | root $base/site; 7 | 8 | # index.php 9 | index index.php; 10 | 11 | # index.php fallback 12 | location / { 13 | try_files $uri $uri/ /index.php?$query_string; 14 | } 15 | 16 | # handle .php 17 | location ~ \.php$ { 18 | fastcgi_pass unix:/run/php/php%VERSION%-fpm.sock; 19 | include nginxconfig/php_fastcgi.conf; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/session_redis.php: -------------------------------------------------------------------------------- 1 | conn = new Hm_Redis($this->site_config); 20 | return $this->conn->is_active(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /assets/data/server_accounts_sample.csv: -------------------------------------------------------------------------------- 1 | server_name;username;password;jmap_server;jmap_hide_from_combined_view;imap_server;imap_port;imap_tls;imap_hide_from_combined_view;smtp_server;smtp_port;smtp_tls;sieve_host;sieve_port;sieve_tls;profile_reply_to;profile_signature;profile_is_default 2 | Mailbox 1;email@example.org;secret;;FALSE;imap.example.org;993;TRUE;FALSE;smtp.example.org;465;TRUE;tls://imap.exemple.org;4190;true;email@example.org;;FALSE 3 | Mailbox 2;test@example2.org;secret2;jmap.example2.org;FALSE;;;;;smtp.example2.org;465;TRUE;tls://jmap.example2.org;4190;false;test@example2.org;my-signature;TRUE -------------------------------------------------------------------------------- /tests/phpunit/page_redirect.php: -------------------------------------------------------------------------------- 1 | assertEquals(null, Hm_Dispatch::page_redirect('test', 200)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/var/log/supervisord.log 4 | pidfile=/var/run/supervisord.pid 5 | 6 | [program:nginx] 7 | command=/usr/sbin/nginx -g "daemon off;" 8 | autostart=true 9 | autorestart=true 10 | stdout_logfile=/dev/stdout 11 | stdout_logfile_maxbytes=0 12 | stderr_logfile=/dev/stderr 13 | stderr_logfile_maxbytes=0 14 | 15 | [program:php-fpm] 16 | command=php-fpm 17 | autostart=true 18 | autorestart=true 19 | stdout_logfile=/dev/stdout 20 | stdout_logfile_maxbytes=0 21 | stderr_logfile=/dev/stderr 22 | stderr_logfile_maxbytes=0 23 | -------------------------------------------------------------------------------- /modules/themes/assets/lux/css/lux.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: lux 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/lux/bootstrap.min.css'); 16 | -------------------------------------------------------------------------------- /modules/themes/assets/yeti/css/yeti.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: yeti 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/yeti/bootstrap.min.css'); 16 | -------------------------------------------------------------------------------- /tests/phpunit/debug.php: -------------------------------------------------------------------------------- 1 | assertTrue(count(Hm_Debug::get()) > 4); 20 | } 21 | /** 22 | * @preserveGlobalState disabled 23 | * @runInSeparateProcess 24 | */ 25 | } 26 | -------------------------------------------------------------------------------- /modules/themes/assets/cosmo/css/cosmo.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: cosmo 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/cosmo/bootstrap.min.css'); 16 | -------------------------------------------------------------------------------- /modules/themes/assets/lumen/css/lumen.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: lumen 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/lumen/bootstrap.min.css'); 16 | -------------------------------------------------------------------------------- /modules/themes/assets/pulse/css/pulse.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: pulse 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/pulse/bootstrap.min.css'); 16 | -------------------------------------------------------------------------------- /modules/contacts/README.md: -------------------------------------------------------------------------------- 1 | ## Contacts 2 | 3 | This module set does not do anything by itself. It provides the framework 4 | for contact sources to be used in Cypht. Currently there are 3 contact 5 | sources supported: 6 | 7 | - local contacts: Basic contact support locally within Cypht 8 | - LDAP contacts: Uses an LDAP server to access/manage contacts 9 | - Gmail contacts: Read-only access to contacts from Gmail accounts 10 | 11 | One or all of those module sets can be enabled with this one to add contact 12 | support for that source. Some work has been done to add CardDav contact 13 | support, however there is much left to do. 14 | -------------------------------------------------------------------------------- /modules/site/modules.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## 🐛 Bug 10 | 11 | 12 | ### Version & Environment 13 | 14 | Rev: [] 15 | 16 | OS: [] 17 | -------------------------------------------------------------------------------- /config/themes.php: -------------------------------------------------------------------------------- 1 | [ 17 | "yourthemefile|Your Theme Name" 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /.github/tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpunit_tests() { 4 | phpunit --bootstrap vendor/autoload.php --configuration tests/phpunit/phpunit.xml --testdox 5 | } 6 | 7 | selenium_tests() { 8 | cp .github/tests/selenium/creds.py tests/selenium/ 9 | cd tests/selenium/ && sh ./runall.sh && cd ../../ 10 | } 11 | 12 | # Main 13 | echo "database: ${DB}" 14 | echo "php-version: ${PHP_V}" 15 | echo "test-arg: ${TEST_ARG}" 16 | 17 | ARG="${TEST_ARG}" 18 | case "$ARG" in 19 | phpunit) 20 | phpunit_tests 21 | ;; 22 | selenium) 23 | selenium_tests 24 | ;; 25 | *) 26 | phpunit_tests 27 | ;; 28 | esac 29 | -------------------------------------------------------------------------------- /modules/pgp/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyPgpPageHandlers() { 2 | $('.priv_title').on("click", function() { $('.priv_keys').toggle(); }); 3 | $('.public_title').on("click", function() { $('.public_keys').toggle(); }); 4 | $('.delete_pgp_key').on("click", function() { return hm_delete_prompt(); }); 5 | $('#priv_key').on("change", function(evt) { Hm_Pgp.read_private_key(evt); }); 6 | Hm_Pgp.list_private_keys(); 7 | if (window.location.hash == '#public_keys') { 8 | $('.public_keys').toggle(); 9 | } 10 | if (window.location.hash == '#private_keys') { 11 | $('.private_keys').toggle(); 12 | } 13 | } -------------------------------------------------------------------------------- /config/wordpress.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'client_id' => env('WORDPRESS_CLIENT_ID', ''), 16 | 'client_secret' => env('WORDPRESS_CLIENT_SECRET', ''), 17 | 'client_uri' => env('WORDPRESS_CLIENT_URI', '') 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /modules/api_login/setup.php: -------------------------------------------------------------------------------- 1 | array('process_api_login'), 15 | 'allowed_post' => array( 16 | 'hm_session' => FILTER_UNSAFE_RAW, 17 | 'hm_id' => FILTER_UNSAFE_RAW, 18 | 'api_login_key' => FILTER_UNSAFE_RAW 19 | ) 20 | ); 21 | -------------------------------------------------------------------------------- /modules/core/js_modules/actions/privacy_controls.js: -------------------------------------------------------------------------------- 1 | async function addSenderToImagesWhitelist(email) { 2 | return new Promise((resolve, reject) => { 3 | Hm_Ajax.request([ 4 | { name: "hm_ajax_hook", value: "ajax_privacy_settings" }, 5 | { name: "images_whitelist", value: email }, 6 | { name: "save_settings", value: true }, 7 | { name: "update", value: true } 8 | ], (response) => { 9 | resolve(response); 10 | }, [], false, undefined, () => { 11 | Hm_Notices.show('An error occured while adding the sender to the whitelist', 'danger'); 12 | reject(); 13 | }); 14 | }); 15 | } -------------------------------------------------------------------------------- /modules/developer/README.md: -------------------------------------------------------------------------------- 1 | ## Developer 2 | 3 | This module set provides tools that are useful for developers adding 4 | features to Cypht. It adds 2 pages to the Settings menu: 5 | 6 | - info: This page shows infromation about the Cypht installation, 7 | the status of existing server connections, and a configuration "map" 8 | that breaks down what modules are associated with what page, with 9 | links to the code online. 10 | 11 | - dev: This page just has some summary information about developing 12 | module sets for Cypht, and has some links to online resources. 13 | 14 | Cypht must be run in debug mode for these pages to be visible, in 15 | production mode they are disabled. 16 | -------------------------------------------------------------------------------- /modules/core/modules.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | DocumentRoot %TRAVIS_BUILD_DIR%/site 6 | 7 | 8 | Options FollowSymLinks MultiViews ExecCGI 9 | AllowOverride All 10 | Require all granted 11 | 12 | 13 | # Wire up Apache to use Travis CI's php-fpm. 14 | 15 | AddHandler php5-fcgi .php 16 | Action php5-fcgi /php5-fcgi 17 | Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi 18 | FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization 19 | 20 | 21 | 22 | Options FollowSymLinks MultiViews ExecCGI 23 | Require all granted 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /modules/core/js_modules/actions/sortCombinedLists.js: -------------------------------------------------------------------------------- 1 | async function sortCombinedLists(sortValue) { 2 | const url = new URL(window.location.href); 3 | url.searchParams.set('sort', sortValue); 4 | 5 | history.pushState(history.state, null, url.toString()); 6 | location.next = url.search; 7 | const messagesStore = new Hm_MessagesStore(getListPathParam(), Hm_Utils.get_url_page_number(), `${getParam('keyword')}_${getParam('filter')}`, sortValue); 8 | try { 9 | Hm_Utils.tbody().attr('id', messagesStore.list); 10 | await messagesStore.load(true, false, false, store => { 11 | display_imap_mailbox(store.rows, store.list, store); 12 | }); 13 | } catch (error) { 14 | Hm_Notices.show('Failed to load messages', 'danger'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/imap/js_modules/utils/messageParts.js: -------------------------------------------------------------------------------- 1 | function handleViewMessagePart() { 2 | $('.msg_part_link').on("click", function(e) { 3 | e.preventDefault(); 4 | const messagePart = $(this).data('messagePart'); 5 | // We could use navigate() and completely change the current route, but inline messages are not rendered under the routing mechanism. 6 | // But that is what would make more sense. TODO: Let's refactor this in the future. 7 | const url = new URL(window.location.href); 8 | url.searchParams.set('part', messagePart); 9 | history.replaceState(history.state, "", url.toString()); 10 | get_message_content(messagePart, getMessageUidParam() ?? inline_msg_uid, getListPathParam(), getParam('list_parent') ?? getListPathParam()); 11 | }); 12 | } -------------------------------------------------------------------------------- /modules/themes/assets/morph/css/morph.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: morph 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/morph/bootstrap.min.css'); 16 | 17 | :root { 18 | --bs-secondary-bg: #c2d0e5; 19 | --bs-input-placeholder-color: var(--bs-body-color); 20 | } 21 | 22 | .text-secondary { 23 | color: #84a7db !important; 24 | } 25 | -------------------------------------------------------------------------------- /.travis/dovecot.sh: -------------------------------------------------------------------------------- 1 | apt-get install -y dovecot-imapd dovecot-lmtpd 2 | sleep 10 3 | stop dovecot 4 | #echo "mail_location = maildir:/home/%u/Maildir" | tee --append /etc/dovecot/conf.d/10-mail.conf 5 | SSL_CERT=/etc/ssl/certs/dovecot.pem 6 | SSL_KEY=/etc/ssl/private/dovecot.pem 7 | PATH=$PATH:/usr/bin/ssl 8 | FQDN=cypht-test.org 9 | MAILNAME=cypht-test.org 10 | (openssl req -new -x509 -days 365 -nodes -out $SSL_CERT -keyout $SSL_KEY <<+ 11 | . 12 | . 13 | . 14 | Dovecot mail server 15 | $FQDN 16 | $FQDN 17 | root@$MAILNAME 18 | + 19 | ) || echo "Warning : Bad SSL config, can't generate certificate." 20 | chown root $SSL_CERT || true 21 | chgrp dovecot $SSL_CERT || true 22 | chmod 0644 $SSL_CERT || true 23 | chown root $SSL_KEY || true 24 | chgrp dovecot $SSL_KEY || true 25 | chmod 0600 $SSL_KEY || true 26 | start dovecot 27 | -------------------------------------------------------------------------------- /modules/themes/assets/sketchy/css/sketchy.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: sketchy 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/sketchy/bootstrap.min.css'); 16 | 17 | .compose_container .bubble { 18 | border-radius: 255px 25px 225px 25px / 25px 225px 25px 255px !important; 19 | border-color: black; 20 | background-color: azure; 21 | } 22 | -------------------------------------------------------------------------------- /tests/phpunit/data/seed.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT OR REPLACE INTO hm_user values('unittestuser', 'sha512:86000:xfEgf7NIUQ2XkeU5tnIcA+HsN8pUllMVdzpJxCSwmbsZAE8Hze3Zs+MeIqepwocYteJ92vhq7pjOfrVThg/p1voELkDdPenU8i2PgG9UTI0IJTGhMN7rsUILgT6XlMAKLp/u2OD13sukUFcQNTdZNFqMsuTVTYw/Me2tAnFwgO4=:rfyUhYsWBCknx6EmbeswN0fy0hAC0N3puXzwWyDRquA='); 3 | 4 | INSERT OR REPLACE INTO hm_user values('testuser', '\$argon2id\$v=19\$m=65536,t=2,p=1\$dw4pTU24zRKHCEkLcloU/A\$9NJm6ALQhVpB2HTHmVHjOai912VhURUDAPsut5lrEa0'); 5 | 6 | INSERT OR REPLACE INTO hm_user_settings values('testuser', 'sFpVPU/hPvmfeiEKUBs4w1EizmbW/Ze2BALZf6kdJrIU3KVZrsqIhKaWTNNFRm3p51ssRAH2mpbxBMhsdpOAqIZMXFHjLttRu9t5WZWOkN7qwEh2LRq6imbkMkfqXg//K294QDLyWjE0Lsc/HSGqnguBF0YUVLVmWmdeqq7/OrXUo4HNbU88i4s2gkukKobJA2hjcOEq/rLOXr3t4LnLlcISnUbt4ptalSbeRrOnx4ehZV8hweQf1E+ID7s/a+8HHx1Qo713JDzReoLEKUsxRQ=='); 7 | -------------------------------------------------------------------------------- /modules/themes/assets/vapor/css/vapor.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: vapor 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url("../../../../../vendor/thomaspark/bootswatch/dist/vapor/bootstrap.min.css"); 16 | 17 | :root { 18 | --bs-secondary-bg: #120625; 19 | --bs-form-bg: #30115e; 20 | --bs-link-color: var(--bs-secondary) !important; 21 | --bs-input-placeholder-color: var(--bs-body-color); 22 | } 23 | -------------------------------------------------------------------------------- /config/github.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'client_id' => env('GITHUB_CLIENT_ID', ''), 14 | 'client_secret' => env('GITHUB_CLIENT_SECRET', ''), 15 | 'redirect_uri' => env('GITHUB_REDIRECT_URI', 'http://localhost/?page=home'), 16 | 'auth_url' => env('GITHUB_AUTH_URL', 'https://github.com/login/oauth/authorize'), 17 | 'token_url' => env('GITHUB_TOKEN_URL', 'https://github.com/login/oauth/access_token'), 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /tests/phpunit/handler_module_debug.php: -------------------------------------------------------------------------------- 1 | parent = build_parent_mock(); 16 | $this->handler_mod = new Hm_Handler_Test($this->parent, 'home'); 17 | } 18 | /** 19 | * @preserveGlobalState disabled 20 | * @runInSeparateProcess 21 | */ 22 | public function test_process_key_debug() { 23 | $this->handler_mod->request->type = 'AJAX'; 24 | $this->assertEquals('exit', $this->handler_mod->process_key()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/themes/assets/solar/css/solar.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: solar 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/solar/bootstrap.min.css'); 16 | 17 | :root { 18 | --bs-secondary-bg: #003542;; 19 | --bs-input-placeholder-color: #657b83; 20 | } 21 | .form-floating>.form-control:disabled~label, .form-floating>:disabled~label { 22 | color: #576a71; 23 | } -------------------------------------------------------------------------------- /modules/profiles/functions.php: -------------------------------------------------------------------------------- 1 | $name, 9 | 'sig' => $signature, 10 | 'rmk' => $remark, 11 | 'smtp_id' => $smtp_server_id, 12 | 'replyto' => $reply_to, 13 | 'default' => $is_default, 14 | 'address' => $email, 15 | 'server' => $server, 16 | 'user' => $user, 17 | 'type' => 'imap' 18 | ); 19 | $id = Hm_Profiles::add($profile); 20 | if ($is_default) { 21 | Hm_Profiles::setDefault($id); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/idle_timer/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Hm_No_Op = { 4 | 'interval': 300, 5 | 'idle_time': 0, 6 | 'reset': function() { 7 | Hm_No_Op.idle_time = 0; 8 | Hm_Timer.cancel(Hm_No_Op.update); 9 | Hm_Timer.add_job(Hm_No_Op.update, Hm_No_Op.interval, true); 10 | }, 11 | 'update': function() { 12 | Hm_No_Op.idle_time += Hm_No_Op.interval; 13 | Hm_Ajax.request( 14 | [{'name': 'hm_ajax_hook', 'value': 'ajax_no_op'}, 15 | {'name': 'idle_time', 'value': Hm_No_Op.idle_time}], 16 | function() { }, 17 | [], 18 | false 19 | ); 20 | return false; 21 | } 22 | }; 23 | 24 | $(function() { 25 | Hm_Timer.add_job(Hm_No_Op.update, Hm_No_Op.interval, true); 26 | $('*').on('click', function() { Hm_No_Op.reset(); }); 27 | }); 28 | -------------------------------------------------------------------------------- /docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | 2 | # this is a demo of using the production cypht image 3 | 4 | services: 5 | db: 6 | image: mariadb:10 7 | ports: 8 | - "3306:3306" 9 | volumes: 10 | - ./data/mysql:/var/lib/mysql 11 | environment: 12 | - MYSQL_ROOT_PASSWORD=root_password 13 | - MYSQL_DATABASE=cypht 14 | - MYSQL_USER=cypht 15 | - MYSQL_PASSWORD=cypht_password 16 | cypht: 17 | image: cypht/cypht:2.4.0 18 | ports: 19 | - "80:80" 20 | # env_file: 21 | # - /etc/cypht-prod.env 22 | environment: 23 | - AUTH_USERNAME=admin 24 | - AUTH_PASSWORD=admin 25 | - DB_CONNECTION_TYPE=host 26 | - DB_DRIVER=mysql 27 | - DB_HOST=db 28 | - DB_NAME=cypht 29 | - DB_USER=cypht 30 | - DB_PASS=cypht_password 31 | - SESSION_TYPE=DB 32 | - USER_CONFIG_TYPE=DB 33 | -------------------------------------------------------------------------------- /modules/pgp/README.md: -------------------------------------------------------------------------------- 1 | ## PGP 2 | 3 | This module set provides EXPERIMENTAL support for sending and decrypting 4 | messages encrypted with PGP. This is initial PGP support, with lots of 5 | missing features and probably bugs. It currently supports: 6 | 7 | - add/delete public keys with an associated E-mail address 8 | - add/delete private keys. These never leave the browser and are destroyed on logout/browser close 9 | - sign/encrypt/both for outbound mail. This is only available for plain text outbound message types, so won't be available if using markdown or HTML 10 | - decrypt on message read. If the message part is recognized as PGP encrypted text, it will provide controls to decrypt it. Regardless of the original content is it will be rendered as plain text 11 | - passphrases are never stored. They must be entered anytime an action is being performed with a private key (sign, decrypt) 12 | -------------------------------------------------------------------------------- /modules/themes/assets/materia/css/materia.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: materia 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/materia/bootstrap.min.css'); 16 | 17 | .compose_container { 18 | box-shadow: inset 0 -1px 0 #ddd; 19 | padding-left: 0ps !important; 20 | } 21 | 22 | .compose_container input { 23 | box-shadow: none !important; 24 | } 25 | 26 | .compose_container .toggle_recipients { 27 | padding: 0px !important; 28 | } -------------------------------------------------------------------------------- /modules/hello_world/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This JS sets up an AJAX request and assigns it to a link on the hello_world page. 5 | * You have access to cash.js functions when this code is loaded, so use the standard 6 | * way to delay actions until page onload if you need to. When the 7 | * site build process is run this code will be combined with JS from other module sets, 8 | * and optionally minified based on the config/app.php file settings. 9 | */ 10 | 11 | /** 12 | * Called when a user clicks on the "AJAX Example" link 13 | */ 14 | var hello_world_ajax_update = function() { 15 | Hm_Ajax.request( 16 | [{'name': 'hm_ajax_hook', 'value': 'ajax_hello_world'}], 17 | update_hello_world_display 18 | ); 19 | }; 20 | 21 | /** 22 | * Callback for hello_world_ajax_update 23 | */ 24 | var update_hello_world_display = function(res) { 25 | alert(res.hello_world_ajax_result); 26 | }; -------------------------------------------------------------------------------- /modules/inline_message/site.css: -------------------------------------------------------------------------------- 1 | .search_content .inline_right, .message_list .inline_right { float: right; width: 50%; max-width: 50%; } 2 | .inline_right .msg_text_inner, .inline_right .msg_text_inner { padding-left: 10px; } 3 | .inline_right .msg_headers th, .inline_right .msg_headers td, .inline_right .msg_headers th, .inline_right .msg_headers td { padding-left: 5px; white-space: normal; overflow-wrap: break-word; } 4 | 5 | .close_inline_msg { float: right; margin-right: 10px; cursor: pointer;} 6 | .search_content .tag_icon, .search_content .add_contact_row, .message_list .tag_icon, .message_list .add_contact_row { display: none; } 7 | .msg_headers th, .msg_headers td{ display: table-cell; border: none; } 8 | .msg_headers th:hover {color: #777 !important; } 9 | .msg_parts th, .msg_parts td { border: none; } 10 | .inline_msg .msg_text, .inline_msg .msg_text_inner { min-height: 100px; } 11 | .inline_msg td { border-bottom: none !important; } 12 | -------------------------------------------------------------------------------- /tests/phpunit/redis_session.php: -------------------------------------------------------------------------------- 1 | config = new Hm_Mock_Config(); 16 | $this->config->set('redis_server', 'asdf'); 17 | $this->config->set('redis_port', 10); 18 | $this->config->set('enable_redis', true); 19 | } 20 | /** 21 | * @preserveGlobalState disabled 22 | * @runInSeparateProcess 23 | */ 24 | public function test_redis_connect() { 25 | $session = new Hm_Redis_Session($this->config, 'Hm_Auth_DB'); 26 | $session->connect(); 27 | $this->assertEquals('Hm_Redis', get_class($session->conn)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/site/setup.php: -------------------------------------------------------------------------------- 1 | array(), 23 | 'allowed_cookie' => array(), 24 | 'allowed_server' => array(), 25 | 'allowed_get' => array(), 26 | 'allowed_post' => array() 27 | ); 28 | -------------------------------------------------------------------------------- /modules/core/js_modules/utils/loaders.js: -------------------------------------------------------------------------------- 1 | function showLoaderToast(text = 'Loading...') { 2 | const uniqueId = Math.random().toString(36).substring(7); 3 | const toastHTML = ` 4 |
5 | 13 |
14 | ` 15 | 16 | document.body.insertAdjacentHTML('beforeend', toastHTML) 17 | 18 | const instance = bootstrap.Toast.getOrCreateInstance(document.getElementById(uniqueId)); 19 | instance.show(); 20 | 21 | return instance; 22 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | ## 🍰 Pullrequest 17 | 18 | 19 | ### Issues 20 | 24 | - None 25 | 26 | ### Todo 27 | 28 | - [X] None 29 | -------------------------------------------------------------------------------- /modules/idle_timer/setup.php: -------------------------------------------------------------------------------- 1 | array( 23 | 'ajax_no_op' 24 | ), 25 | 'allowed_post' => array( 26 | 'idle_time' => FILTER_VALIDATE_INT 27 | ) 28 | ); 29 | -------------------------------------------------------------------------------- /modules/themes/assets/superhero/css/superhero.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: superhero 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/superhero/bootstrap.min.css'); 16 | 17 | :root { 18 | --bs-secondary-bg: #132f46;; 19 | --bs-input-placeholder-color: #868e96; 20 | } 21 | 22 | .form-floating>.form-control-plaintext~label, .form-floating>.form-control:focus~label, .form-floating>.form-control:not(:placeholder-shown)~label, .form-floating>.form-select~label { 23 | color: var(--bs-input-placeholder-color) !important; 24 | } -------------------------------------------------------------------------------- /tests/phpunit/data/seed_mysql.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO hm_user (username, hash) 2 | VALUES ('unittestuser', 'sha512:86000:xfEgf7NIUQ2XkeU5tnIcA+HsN8pUllMVdzpJxCSwmbsZAE8Hze3Zs+MeIqepwocYteJ92vhq7pjOfrVThg/p1voELkDdPenU8i2PgG9UTI0IJTGhMN7rsUILgT6XlMAKLp/u2OD13sukUFcQNTdZNFqMsuTVTYw/Me2tAnFwgO4=:rfyUhYsWBCknx6EmbeswN0fy0hAC0N3puXzwWyDRquA=') 3 | ON DUPLICATE KEY UPDATE hash = VALUES(hash); 4 | 5 | INSERT INTO hm_user (username, hash) 6 | VALUES ('testuser', '\$argon2id\$v=19\$m=65536,t=2,p=1\$dw4pTU24zRKHCEkLcloU/A\$9NJm6ALQhVpB2HTHmVHjOai912VhURUDAPsut5lrEa0') 7 | ON DUPLICATE KEY UPDATE hash = VALUES(hash); 8 | 9 | INSERT INTO hm_user_settings (username, settings) 10 | VALUES ('testuser', 'sFpVPU/hPvmfeiEKUBs4w1EizmbW/Ze2BALZf6kdJrIU3KVZrsqIhKaWTNNFRm3p51ssRAH2mpbxBMhsdpOAqIZMXFHjLttRu9t5WZWOkN7qwEh2LRq6imbkMkfqXg//K294QDLyWjE0Lsc/HSGqnguBF0YUVLVmWmdeqq7/OrXUo4HNbU88i4s2gkukKobJA2hjcOEq/rLOXr3t4LnLlcISnUbt4ptalSbeRrOnx4ehZV8hweQf1E+ID7s/a+8HHx1Qo713JDzReoLEKUsxRQ==') 11 | ON DUPLICATE KEY UPDATE settings = VALUES(settings); 12 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | APP_DIR=/usr/local/share/cypht 6 | cd ${APP_DIR} 7 | 8 | # TODO: validate env var values here, perhaps in php or in Hm_Site_Config_File() 9 | 10 | # TODO: source these defaults from an .env file or some other place? 11 | USER_CONFIG_TYPE="${USER_CONFIG_TYPE:-file}" 12 | USER_SETTINGS_DIR="${USER_SETTINGS_DIR:-/var/lib/hm3/users}" 13 | ATTACHMENT_DIR="${ATTACHMENT_DIR:-/var/lib/hm3/attachments}" 14 | 15 | # Wait for database to be ready then setup tables 16 | ./scripts/setup_database.php 17 | 18 | # Setup filesystem and users 19 | ./scripts/setup_system.sh 20 | 21 | # Enable the program in the web-server 22 | 23 | if [ "${USER_CONFIG_TYPE}" = "file" ] 24 | then 25 | chown www-data:www-data ${USER_SETTINGS_DIR} 26 | fi 27 | 28 | chown www-data:www-data ${ATTACHMENT_DIR} 29 | chown -R www-data:www-data /var/lib/nginx 30 | 31 | rm -r /var/www 32 | ln -s $(pwd)/site /var/www 33 | 34 | # Start services 35 | exec /usr/bin/supervisord -c /etc/supervisord.conf 36 | -------------------------------------------------------------------------------- /modules/themes/assets/slate/css/slate.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: slate 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url('../../../../../vendor/thomaspark/bootswatch/dist/slate/bootstrap.min.css'); 16 | 17 | :root { 18 | --bs-secondary-bg: #3b4047;; 19 | --bs-input-placeholder-color: rgba(170, 170, 170, 0.75); 20 | --bs-primary: #fafafa; 21 | } 22 | 23 | .compose_container .toggle_recipients { 24 | color: var(--bs-secondary-bg); 25 | } 26 | 27 | .compose_container .toggle_recipients:hover{ 28 | color: #282b30; 29 | } 30 | 31 | table a { 32 | color: var(--bs-primary) !important; 33 | } -------------------------------------------------------------------------------- /modules/themes/assets/darkly/css/darkly.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*Bootswatch theme adapted by the Cypht Community for Cypht */ 3 | /* Original Bootswatch information: 4 | * Bootswatch v5.3.1 (https://bootswatch.com) 5 | * Theme: darkly 6 | * Copyright 2012-2023 Thomas Park 7 | * Licensed under MIT 8 | * Based on Bootstrap 9 | */ 10 | /*! 11 | * Bootstrap v5.3.1 (https://getbootstrap.com/) 12 | * Copyright 2011-2023 The Bootstrap Authors 13 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 14 | */ 15 | @import url("../../../../../vendor/thomaspark/bootswatch/dist/darkly/bootstrap.min.css"); 16 | 17 | :root { 18 | --bs-secondary-bg: #333; 19 | --bs-input-placeholder-color: #888; 20 | } 21 | .form-floating > .form-control-plaintext ~ label, 22 | .form-floating > .form-control:focus ~ label, 23 | .form-floating > .form-control:not(:placeholder-shown) ~ label, 24 | .form-floating > .form-select ~ label { 25 | color: #888 !important; 26 | } 27 | .text-secondary { 28 | color: #adadad !important; 29 | } 30 | -------------------------------------------------------------------------------- /scripts/setup_system.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This script is for creating directories and generating the config 4 | 5 | set -e 6 | 7 | SCRIPT_DIR=$(dirname $(realpath "$0")) 8 | 9 | # TODO: source these defaults from an .env file or some other place? 10 | USER_CONFIG_TYPE="${USER_CONFIG_TYPE:-file}" 11 | USER_SETTINGS_DIR="${USER_SETTINGS_DIR:-/var/lib/hm3/users}" 12 | ATTACHMENT_DIR="${ATTACHMENT_DIR:-/var/lib/hm3/attachments}" 13 | 14 | if [ "${USER_CONFIG_TYPE}" = "file" ] 15 | then 16 | echo "Creating directory for settings ${USER_SETTINGS_DIR}" 17 | mkdir -p ${USER_SETTINGS_DIR} 18 | fi 19 | 20 | echo "Creating directory for attachments ${ATTACHMENT_DIR}" 21 | mkdir -p ${ATTACHMENT_DIR} 22 | 23 | # TODO: should a user be created if USER_CONFIG_TYPE=file ? 24 | if [ "${USER_CONFIG_TYPE}" = "DB" ] && [ -n "${AUTH_USERNAME}" ] 25 | then 26 | php ${SCRIPT_DIR}/../scripts/create_account.php ${AUTH_USERNAME} ${AUTH_PASSWORD} 27 | fi 28 | 29 | # Generate the run-time configuration 30 | php ${SCRIPT_DIR}/../scripts/config_gen.php 31 | -------------------------------------------------------------------------------- /database/pgsql_schema.sql: -------------------------------------------------------------------------------- 1 | DO $$ 2 | BEGIN 3 | IF NOT EXISTS (SELECT 1 FROM information_schema.tables 4 | WHERE table_name = 'hm_user') THEN 5 | CREATE TABLE hm_user ( 6 | username VARCHAR(255) PRIMARY KEY, 7 | hash VARCHAR(255) 8 | ); 9 | END IF; 10 | END $$; 11 | 12 | DO $$ 13 | BEGIN 14 | IF NOT EXISTS (SELECT 1 FROM information_schema.tables 15 | WHERE table_name = 'hm_user_session') THEN 16 | CREATE TABLE hm_user_session ( 17 | hm_id VARCHAR(255) PRIMARY KEY, 18 | data BYTEA, 19 | date TIMESTAMP, 20 | hm_version INT DEFAULT 1 21 | ); 22 | END IF; 23 | END $$; 24 | 25 | DO $$ 26 | BEGIN 27 | IF NOT EXISTS (SELECT 1 FROM information_schema.tables 28 | WHERE table_name = 'hm_user_settings') THEN 29 | CREATE TABLE hm_user_settings ( 30 | username VARCHAR(255) PRIMARY KEY, 31 | settings BYTEA 32 | ); 33 | END IF; 34 | END $$; 35 | -------------------------------------------------------------------------------- /tests/phpunit/data/seed_postgres.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO hm_user (username, hash) 2 | VALUES ('unittestuser', 'sha512:86000:xfEgf7NIUQ2XkeU5tnIcA+HsN8pUllMVdzpJxCSwmbsZAE8Hze3Zs+MeIqepwocYteJ92vhq7pjOfrVThg/p1voELkDdPenU8i2PgG9UTI0IJTGhMN7rsUILgT6XlMAKLp/u2OD13sukUFcQNTdZNFqMsuTVTYw/Me2tAnFwgO4=:rfyUhYsWBCknx6EmbeswN0fy0hAC0N3puXzwWyDRquA=') 3 | ON CONFLICT (username) DO UPDATE SET hash = EXCLUDED.hash; 4 | 5 | INSERT INTO hm_user (username, hash) 6 | VALUES ('testuser', '\$argon2id\$v=19\$m=65536,t=2,p=1\$dw4pTU24zRKHCEkLcloU/A\$9NJm6ALQhVpB2HTHmVHjOai912VhURUDAPsut5lrEa0') 7 | ON CONFLICT (username) DO UPDATE SET hash = EXCLUDED.hash; 8 | 9 | INSERT INTO hm_user_settings (username, settings) 10 | VALUES ('testuser', 'sFpVPU/hPvmfeiEKUBs4w1EizmbW/Ze2BALZf6kdJrIU3KVZrsqIhKaWTNNFRm3p51ssRAH2mpbxBMhsdpOAqIZMXFHjLttRu9t5WZWOkN7qwEh2LRq6imbkMkfqXg//K294QDLyWjE0Lsc/HSGqnguBF0YUVLVmWmdeqq7/OrXUo4HNbU88i4s2gkukKobJA2hjcOEq/rLOXr3t4LnLlcISnUbt4ptalSbeRrOnx4ehZV8hweQf1E+ID7s/a+8HHx1Qo713JDzReoLEKUsxRQ==') 11 | ON CONFLICT (username) DO UPDATE SET settings = EXCLUDED.settings; 12 | -------------------------------------------------------------------------------- /tests/phpunit/output.php: -------------------------------------------------------------------------------- 1 | http = new Hm_Output_HTTP(); 14 | } 15 | /** 16 | * @preserveGlobalState disabled 17 | * @runInSeparateProcess 18 | */ 19 | public function test_send_response() { 20 | ob_start(); 21 | ob_start(); 22 | $this->http->send_response('test', array('http_headers' => array('test'))); 23 | $output = ob_get_contents(); 24 | ob_end_clean(); 25 | $this->assertEquals('test', $output); 26 | ob_start(); 27 | ob_start(); 28 | $this->http->send_response('test', array()); 29 | $output = ob_get_contents(); 30 | ob_end_clean(); 31 | $this->assertEquals('test', $output); 32 | } 33 | public function tearDown(): void { 34 | unset($this->http); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/Daily-Image.yml: -------------------------------------------------------------------------------- 1 | name: Build Daily Image 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | jobs: 7 | build-and-push: 8 | if: github.repository_owner == 'cypht-org' 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v2 19 | 20 | 21 | - name: Log in to Docker Hub 22 | uses: docker/login-action@v2 23 | with: 24 | username: ${{ secrets.DOCKER_USERNAME }} 25 | password: ${{ secrets.DOCKER_PASSWORD }} 26 | 27 | - name: Build and push Docker image 28 | uses: docker/build-push-action@v4 29 | with: 30 | context: . 31 | platforms: linux/amd64 32 | file: ./docker/Dockerfile 33 | push: true 34 | tags: cypht/cypht:daily 35 | debug: true 36 | 37 | - name: Log out from Docker Hub 38 | run: docker logout -------------------------------------------------------------------------------- /modules/gmail_contacts/setup.php: -------------------------------------------------------------------------------- 1 | 'vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js', 5 | 'cash' => 'third_party/cash.min.js', 6 | 'resumable' => 'third_party/resumable.min.js', 7 | 'ays-beforeunload-shim' => 'third_party/ays-beforeunload-shim.js', 8 | 'jquery-are-you-sure' => 'third_party/jquery.are-you-sure.js', 9 | 'sortable' => 'third_party/sortable.min.js', 10 | 'kindeditor' => 'third_party/kindeditor/kindeditor-all-min.js', 11 | ]); 12 | 13 | function get_js_libs($exclude_deps = []) { 14 | $js_lib = ''; 15 | 16 | foreach (JS_LIBS as $dep) { 17 | if (!in_array($dep, $exclude_deps)) { 18 | $js_lib .= ''; 19 | } 20 | } 21 | return $js_lib; 22 | } 23 | 24 | function get_js_libs_content($exclude_deps = []) { 25 | $js_lib = ''; 26 | 27 | foreach (JS_LIBS as $key => $dep) { 28 | if (!in_array($key, $exclude_deps)) { 29 | $js_lib .= file_get_contents(APP_PATH.$dep); 30 | } 31 | } 32 | return $js_lib; 33 | } 34 | -------------------------------------------------------------------------------- /scripts/load.php: -------------------------------------------------------------------------------- 1 | 6 | * @site http://www.kindsoft.net/ 7 | * @licence http://www.kindsoft.net/license.php 8 | *******************************************************************************/ 9 | 10 | KindEditor.plugin('pagebreak', function(K) { 11 | var self = this; 12 | var name = 'pagebreak'; 13 | var pagebreakHtml = K.undef(self.pagebreakHtml, '
'); 14 | 15 | self.clickToolbar(name, function() { 16 | var cmd = self.cmd, range = cmd.range; 17 | self.focus(); 18 | var tail = self.newlineTag == 'br' || K.WEBKIT ? '' : ''; 19 | self.insertHtml(pagebreakHtml + tail); 20 | if (tail !== '') { 21 | var p = K('#__kindeditor_tail_tag__', self.edit.doc); 22 | range.selectNodeContents(p[0]); 23 | p.removeAttr('id'); 24 | cmd.select(); 25 | } 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /.travis/creds.py-edge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os, sys 4 | from selenium import webdriver 5 | 6 | RECIP='testuser@localhost.localdomain' 7 | IMAP_ID='2' 8 | DESIRED_CAP = None 9 | SITE_URL = 'http://cypht-test.org/' 10 | USER = 'testuser' 11 | PASS = 'testuser' 12 | 13 | config = { 14 | 'user': os.environ['BROWSERSTACK_USER'], 15 | 'key': os.environ['BROWSERSTACK_KEY'], 16 | 'id': os.environ['BROWSERSTACK_LOCAL_IDENTIFIER'], 17 | 'name': os.environ['TEST_SUITE'].upper().replace('_', ' ')[:-3] 18 | } 19 | 20 | def get_driver(cap): 21 | capabilities = { 22 | 'browserstack.local': 'true', 23 | 'browserstack.localIdentifier': config['id'], 24 | 'os': 'Windows', 25 | 'os_version': '10', 26 | 'browser': 'Edge', 27 | 'resolution': '1920x1080', 28 | 'name': config['name'] 29 | } 30 | driver_url='http://{0}:{1}@hub-cloud.browserstack.com/wd/hub'.format(config['user'], config['key']) 31 | print "BROWSER {0}".format(capabilities['browser']) 32 | return webdriver.Remote(desired_capabilities=capabilities, command_executor=driver_url) 33 | 34 | def success(driver): 35 | pass 36 | -------------------------------------------------------------------------------- /.travis/creds.py-ff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os, sys 4 | from selenium import webdriver 5 | 6 | RECIP='testuser@localhost.localdomain' 7 | IMAP_ID='2' 8 | DESIRED_CAP = None 9 | SITE_URL = 'http://cypht-test.org/' 10 | USER = 'testuser' 11 | PASS = 'testuser' 12 | 13 | config = { 14 | 'user': os.environ['BROWSERSTACK_USER'], 15 | 'key': os.environ['BROWSERSTACK_KEY'], 16 | 'id': os.environ['BROWSERSTACK_LOCAL_IDENTIFIER'], 17 | 'name': os.environ['TEST_SUITE'].upper().replace('_', ' ')[:-3] 18 | } 19 | 20 | def get_driver(cap): 21 | capabilities = { 22 | 'browserstack.local': 'true', 23 | 'browserstack.localIdentifier': config['id'], 24 | 'os': 'Windows', 25 | 'os_version': '10', 26 | 'browser': 'Firefox', 27 | 'resolution': '1920x1080', 28 | 'name': config['name'] 29 | } 30 | driver_url='http://{0}:{1}@hub-cloud.browserstack.com/wd/hub'.format(config['user'], config['key']) 31 | print "BROWSER {0}".format(capabilities['browser']) 32 | return webdriver.Remote(desired_capabilities=capabilities, command_executor=driver_url) 33 | 34 | def success(driver): 35 | pass 36 | -------------------------------------------------------------------------------- /.travis/creds.py-chrome: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os, sys 4 | from selenium import webdriver 5 | 6 | RECIP='testuser@localhost.localdomain' 7 | IMAP_ID='2' 8 | DESIRED_CAP = None 9 | SITE_URL = 'http://cypht-test.org/' 10 | USER = 'testuser' 11 | PASS = 'testuser' 12 | 13 | config = { 14 | 'user': os.environ['BROWSERSTACK_USER'], 15 | 'key': os.environ['BROWSERSTACK_KEY'], 16 | 'id': os.environ['BROWSERSTACK_LOCAL_IDENTIFIER'], 17 | 'name': os.environ['TEST_SUITE'].upper().replace('_', ' ')[:-3] 18 | } 19 | 20 | def get_driver(cap): 21 | capabilities = { 22 | 'browserstack.local': 'true', 23 | 'browserstack.localIdentifier': config['id'], 24 | 'os': 'Windows', 25 | 'os_version': '10', 26 | 'browser': 'Chrome', 27 | 'name': config['name'], 28 | 'resolution': '1920x1080' 29 | } 30 | driver_url='http://{0}:{1}@hub-cloud.browserstack.com/wd/hub'.format(config['user'], config['key']) 31 | print "BROWSER {0}".format(capabilities['browser']) 32 | return webdriver.Remote(desired_capabilities=capabilities, command_executor=driver_url) 33 | 34 | def success(driver): 35 | pass 36 | -------------------------------------------------------------------------------- /third_party/ays-beforeunload-shim.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * An experimental shim to partially emulate onBeforeUnload on iOS. 3 | * Part of https://github.com/codedance/jquery.AreYouSure/ 4 | * 5 | * Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/ 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | * 9 | * Author: chris.dance@papercut.com 10 | * Date: 19th May 2014 11 | */ 12 | $(function() { 13 | if (!navigator.userAgent.toLowerCase().match(/iphone|ipad|ipod|opera/)) { 14 | return; 15 | } 16 | $('a').on('click', function(evt) { 17 | var href = $(evt.target).closest('a').attr('href'); 18 | if (href !== undefined && !(href.match(/^#/) || href.trim() == '')) { 19 | var response = $(window).triggerHandler('beforeunload', response); 20 | if (response && response != "") { 21 | var msg = response + "\n\n" 22 | + "Press OK to leave this page or Cancel to stay."; 23 | if (!confirm(msg)) { 24 | return false; 25 | } 26 | } 27 | window.location.href = href; 28 | return false; 29 | } 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /modules/history/setup.php: -------------------------------------------------------------------------------- 1 | array( 21 | 'history', 22 | ) 23 | ); 24 | -------------------------------------------------------------------------------- /modules/recover_settings/setup.php: -------------------------------------------------------------------------------- 1 | array('recover_settings'), 19 | 'allowed_post' => array( 20 | 'old_password_recover' => FILTER_UNSAFE_RAW, 21 | 'new_password_recover' => FILTER_UNSAFE_RAW, 22 | 'recover_settings' => FILTER_UNSAFE_RAW 23 | ) 24 | ); 25 | -------------------------------------------------------------------------------- /.travis/creds.py-safari: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os, sys 4 | from selenium import webdriver 5 | 6 | RECIP='testuser@localhost.localdomain' 7 | IMAP_ID='2' 8 | DESIRED_CAP = None 9 | SITE_URL = 'http://cypht-test.org/' 10 | USER = 'testuser' 11 | PASS = 'testuser' 12 | 13 | config = { 14 | 'user': os.environ['BROWSERSTACK_USER'], 15 | 'key': os.environ['BROWSERSTACK_KEY'], 16 | 'id': os.environ['BROWSERSTACK_LOCAL_IDENTIFIER'], 17 | 'name': os.environ['TEST_SUITE'].upper().replace('_', ' ')[:-3] 18 | } 19 | 20 | def get_driver(cap): 21 | capabilities = { 22 | 'browserstack.local': 'true', 23 | 'browserstack.localIdentifier': config['id'], 24 | 'browserstack.selenium_version' : '3.5.2', 25 | 'os': 'OS X', 26 | 'os_version': 'Sierra', 27 | 'browser': 'Safari', 28 | 'resolution': '1920x1080', 29 | 'name': config['name'] 30 | } 31 | driver_url='http://{0}:{1}@hub-cloud.browserstack.com/wd/hub'.format(config['user'], config['key']) 32 | print "BROWSER {0}".format(capabilities['browser']) 33 | return webdriver.Remote(desired_capabilities=capabilities, command_executor=driver_url) 34 | 35 | def success(driver): 36 | pass 37 | -------------------------------------------------------------------------------- /third_party/kindeditor/plugins/preview/preview.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * KindEditor - WYSIWYG HTML Editor for Internet 3 | * Copyright (C) 2006-2011 kindsoft.net 4 | * 5 | * @author Roddy 6 | * @site http://www.kindsoft.net/ 7 | * @licence http://www.kindsoft.net/license.php 8 | *******************************************************************************/ 9 | 10 | KindEditor.plugin('preview', function(K) { 11 | var self = this, name = 'preview', undefined; 12 | self.clickToolbar(name, function() { 13 | var lang = self.lang(name + '.'), 14 | html = '
' + 15 | '' + 16 | '
', 17 | dialog = self.createDialog({ 18 | name : name, 19 | width : 750, 20 | title : self.lang(name), 21 | body : html 22 | }), 23 | iframe = K('iframe', dialog.div), 24 | doc = K.iframeDoc(iframe); 25 | doc.open(); 26 | doc.write(self.fullHtml()); 27 | doc.close(); 28 | K(doc.body).css('background-color', '#FFF'); 29 | iframe[0].contentWindow.focus(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /modules/2fa/setup.php: -------------------------------------------------------------------------------- 1 | array( 23 | 'ajax_2fa_setup_check', 24 | ), 25 | 'allowed_post' => array( 26 | '2fa_code' => FILTER_UNSAFE_RAW, 27 | '2fa_enable' => FILTER_VALIDATE_INT, 28 | '2fa_backup_codes' => array('filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_FORCE_ARRAY) 29 | ), 30 | 'allowed_output' => array( 31 | 'ajax_2fa_verified' => array(FILTER_VALIDATE_BOOLEAN, false), 32 | ), 33 | ); 34 | -------------------------------------------------------------------------------- /modules/themes/assets/default/css/default.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* The Bootstrap default theme, for Cypht */ 3 | /*! 4 | * Bootstrap v5.3.2 (https://getbootstrap.com/) 5 | * Copyright 2011-2023 The Bootstrap Authors 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 7 | */ 8 | @import url("../../../../../vendor/twbs/bootstrap/dist/css/bootstrap.min.css"); 9 | 10 | :root { 11 | --bs-primary: teal; 12 | --bs-link-color: teal; 13 | --bs-link-hover-color: #006363; 14 | --bs-primary-rgb: 0, 128, 128; 15 | } 16 | 17 | .btn-primary { 18 | --bs-btn-color: #fff; 19 | --bs-btn-bg: teal; 20 | --bs-btn-border-color: var(--bs-primary); 21 | --bs-btn-hover-color: #fff; 22 | --bs-btn-hover-bg: #006363; 23 | --bs-btn-hover-border-color: #005555; 24 | --bs-btn-focus-shadow-rgb: 0, 128, 128; 25 | --bs-btn-active-color: #fff; 26 | --bs-btn-active-bg: #005555; 27 | --bs-btn-active-border-color: #005555; 28 | --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 29 | --bs-btn-disabled-color: #fff; 30 | --bs-btn-disabled-bg: teal; 31 | --bs-btn-disabled-border-color: teal; 32 | } 33 | .text-bg-primary { 34 | color: #fff !important; 35 | background-color: teal !important; 36 | } 37 | -------------------------------------------------------------------------------- /tests/phpunit/module_exec_debug.php: -------------------------------------------------------------------------------- 1 | module_exec = new Hm_Module_Exec($config); 13 | } 14 | /** 15 | * @preserveGlobalState disabled 16 | * @runInSeparateProcess 17 | */ 18 | public function test_process_module_setup() { 19 | $this->module_exec->process_module_setup(); 20 | $this->assertEquals(array('allowed_output' => array(), 'allowed_post' => array(), 'allowed_get' => array(), 'allowed_cookie' => array(), 'allowed_server' => array(), 'allowed_pages' => array ()), $this->module_exec->filters); 21 | } 22 | /** 23 | * @preserveGlobalState disabled 24 | * @runInSeparateProcess 25 | */ 26 | public function test_setup_debug_modules() { 27 | $this->module_exec->site_config->mods = array('core'); 28 | $this->module_exec->setup_debug_modules(); 29 | $this->assertTrue(!empty($this->module_exec->filters['allowed_pages'])); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/smtp/js_modules/[kindeditor]/kindEditor.js: -------------------------------------------------------------------------------- 1 | let K; 2 | KindEditor.ready(function(k) { 3 | K = k; 4 | }); 5 | function useKindEditor() { 6 | 7 | window.kindEditor = K.create("#compose_body", { 8 | items: [ 9 | "formatblock", 10 | "fontname", 11 | "fontsize", 12 | "forecolor", 13 | "hilitecolor", 14 | "bold", 15 | "italic", 16 | "underline", 17 | "strikethrough", 18 | "lineheight", 19 | "table", 20 | "hr", 21 | "pagebreak", 22 | "link", 23 | "unlink", 24 | "justifyleft", 25 | "justifycenter", 26 | "justifyright", 27 | "justifyfull", 28 | "insertorderedlist", 29 | "insertunorderedlist", 30 | "indent", 31 | "outdent", 32 | "|", 33 | "undo", 34 | "redo", 35 | "preview", 36 | "print", 37 | "|", 38 | "selectall", 39 | "cut", 40 | "copy", 41 | "paste", 42 | "plainpaste", 43 | "wordpaste", 44 | "|", 45 | "source", 46 | "fullscreen", 47 | ], 48 | basePath: "third_party/kindeditor/", 49 | }); 50 | } 51 | 52 | // useKindEditor(); 53 | -------------------------------------------------------------------------------- /.github/tests/selenium/creds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from selenium import webdriver 4 | from selenium.webdriver.chrome.options import Options 5 | from selenium.webdriver.chrome.service import Service 6 | 7 | chrome_options = Options() 8 | chrome_options.add_argument('--headless') 9 | chrome_options.add_argument('--disable-gpu') 10 | chrome_options.BinaryLocation = "/usr/bin/google-chrome" 11 | 12 | chrome_options.headless = False 13 | chrome_options.add_argument("start-maximized") 14 | # options.add_experimental_option("detach", True) 15 | chrome_options.add_argument("--no-sandbox") 16 | chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) 17 | chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) 18 | chrome_options.add_experimental_option('useAutomationExtension', False) 19 | chrome_options.add_argument('--disable-blink-features=AutomationControlled') 20 | 21 | RECIP='testuser@localhost.org' 22 | IMAP_ID='0' 23 | DRIVER_CMD =Service('/usr/bin/chromedriver') 24 | SITE_URL = 'http://cypht-test.org/' 25 | USER = 'testuser' 26 | PASS = 'testuser' 27 | DESIRED_CAP = None 28 | 29 | def get_driver(cap): 30 | return webdriver.Chrome(service=DRIVER_CMD, options=chrome_options) 31 | 32 | def success(driver): 33 | pass 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/Release-Image.yml: -------------------------------------------------------------------------------- 1 | name: Build Release Image 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build-and-push: 9 | if: github.repository_owner == 'cypht-org' 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | 21 | 22 | - name: Log in to Docker Hub 23 | uses: docker/login-action@v2 24 | with: 25 | username: ${{ secrets.DOCKER_USERNAME }} 26 | password: ${{ secrets.DOCKER_PASSWORD }} 27 | 28 | - name: Get tags 29 | id: tags 30 | run: | 31 | echo "tags=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT 32 | 33 | - name: Build and push Docker image 34 | uses: docker/build-push-action@v4 35 | with: 36 | context: . 37 | platforms: linux/amd64 38 | file: ./docker/Dockerfile 39 | push: true 40 | tags: | 41 | cypht/cypht:${{ steps.tags.outputs.tags }} 42 | cypht/cypht:latest 43 | debug: false 44 | 45 | - name: Log out from Docker Hub 46 | run: docker logout -------------------------------------------------------------------------------- /modules/core/js_modules/markup/pagination.js: -------------------------------------------------------------------------------- 1 | const paginationMarkup = (totalPages) => { 2 | const currentPage = getParam('list_page') || 1; 3 | const markup = ` 4 | 15 | ` 16 | 17 | return markup; 18 | } 19 | 20 | function handlePagination() { 21 | $(".pagination .next").on("click", nextPage); 22 | $(".pagination .prev").on("click", previousPage); 23 | } 24 | 25 | function showPagination (totalPages) { 26 | if ($('.message_list .pagination').length) { 27 | $('.message_list .pagination').remove(); 28 | } 29 | if (totalPages > 1) { 30 | $(paginationMarkup(totalPages)).insertBefore('.message_table'); 31 | handlePagination(); 32 | refreshNextButton(getParam('list_page') || 1); 33 | refreshPreviousButton(getParam('list_page') || 1); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/phpunit/bootstrap.php: -------------------------------------------------------------------------------- 1 | load('.env.example'); 40 | /* set the default since and per_source values */ 41 | $environment->define_default_constants($mock_config); -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | DOCKERHUB_REPO=cypht/cypht 3 | 4 | .PHONY: docker-up 5 | docker-up: ## start docker stack in foreground for development 6 | docker compose -f docker-compose.dev.yaml up --build || true # --abort-on-container-exit 7 | 8 | .PHONY: docker-push 9 | .ONESHELL: 10 | docker-push: ## build, tag, and push image to dockerhub. presumes you are logged in. run with a version like tag:1.2.3 11 | @[ "$(tag)" = "" ] && (echo "Tag required. Example tag=1.2.3" ; exit 1) 12 | @image=$(DOCKERHUB_REPO):$(tag) 13 | @echo "Building image $${image}" 14 | @docker buildx build . --platform linux/386,linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \ 15 | -t $${image} -f docker/Dockerfile --push 16 | 17 | .PHONY: dockerhub-push-readme 18 | .ONESHELL: 19 | dockerhub-push-readme: ## upload readme to dockerhub 20 | docker pushrm --file docker/DOCKERHUB-README.md $(DOCKERHUB_REPO) 21 | 22 | .PHONY: setup 23 | .ONESHELL: 24 | setup: ## locally setup app and users. presumes env vars are set 25 | set -e 26 | echo "Installing dependencies" 27 | composer install 28 | echo "Creating tables and user" 29 | ./scripts/setup_database.php 30 | echo "Creating directories and configs" 31 | ./scripts/setup_system.sh 32 | 33 | help: ## get help 34 | @grep -E '^[a-zA-Z_-]+:.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 35 | -------------------------------------------------------------------------------- /modules/sievefilters/hm-sieve.php: -------------------------------------------------------------------------------- 1 | 2 | connect($imap_account['user'], $imap_account['pass'], $sieve_tls, "", "PLAIN"); 20 | return $client; 21 | } else { 22 | $errorMsg = 'Invalid config host'; 23 | if (isset($imap_account['name'])) { 24 | $errorMsg .= ' for ' . $imap_account['name']; 25 | } 26 | throw new Exception($errorMsg); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /third_party/kindeditor/plugins/lineheight/lineheight.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * KindEditor - WYSIWYG HTML Editor for Internet 3 | * Copyright (C) 2006-2011 kindsoft.net 4 | * 5 | * @author Roddy 6 | * @site http://www.kindsoft.net/ 7 | * @licence http://www.kindsoft.net/license.php 8 | *******************************************************************************/ 9 | 10 | KindEditor.plugin('lineheight', function(K) { 11 | var self = this, name = 'lineheight', lang = self.lang(name + '.'); 12 | self.clickToolbar(name, function() { 13 | var curVal = '', commonNode = self.cmd.commonNode({'*' : '.line-height'}); 14 | if (commonNode) { 15 | curVal = commonNode.css('line-height'); 16 | } 17 | var menu = self.createMenu({ 18 | name : name, 19 | width : 150 20 | }); 21 | K.each(lang.lineHeight, function(i, row) { 22 | K.each(row, function(key, val) { 23 | menu.addItem({ 24 | title : val, 25 | checked : curVal === key, 26 | click : function() { 27 | self.cmd.toggle('', { 28 | span : '.line-height=' + key 29 | }); 30 | self.updateState(); 31 | self.addBookmark(); 32 | self.hideMenu(); 33 | } 34 | }); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /docker-compose.dev.yaml: -------------------------------------------------------------------------------- 1 | 2 | # this file should be used for development, not production 3 | 4 | services: 5 | db: 6 | image: mariadb:10 7 | ports: 8 | - "3306:3306" 9 | volumes: 10 | - ./data/mysql:/var/lib/mysql 11 | environment: 12 | - MYSQL_ROOT_PASSWORD=root_password 13 | - MYSQL_DATABASE=cypht 14 | - MYSQL_USER=cypht 15 | - MYSQL_PASSWORD=cypht_password 16 | stop_grace_period: 30s 17 | cypht: 18 | build: 19 | context: . 20 | dockerfile: ./docker/Dockerfile 21 | args: 22 | WITH_DEBUG: true 23 | volumes: 24 | - ./data/users:/var/lib/hm3/users 25 | - ./data/attachments:/var/lib/hm3/attachments 26 | - ./data/app_data:/var/lib/hm3/app_data 27 | - ./data/sqlite:/var/lib/hm3/sqlite 28 | # The following allow for some live code updates during development 29 | - ./lib:/usr/local/share/cypht/lib 30 | - ./modules:/usr/local/share/cypht/modules 31 | ports: 32 | - "80:80" 33 | environment: 34 | - AUTH_USERNAME=admin 35 | - AUTH_PASSWORD=admin 36 | - DB_CONNECTION_TYPE=host 37 | - DB_DRIVER=mysql 38 | - DB_HOST=db 39 | - DB_NAME=cypht 40 | - DB_USER=cypht 41 | - DB_PASS=cypht_password 42 | - SESSION_TYPE=DB 43 | - USER_CONFIG_TYPE=DB 44 | extra_hosts: 45 | host.docker.internal: host-gateway # for xdebug 46 | stop_grace_period: 30s 47 | -------------------------------------------------------------------------------- /tests/phpunit/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # example run: ./run.sh --filter=Hm_Test_Core_Message_Functions 4 | 5 | set -e 6 | 7 | SCRIPT_DIR=$(dirname $(realpath "$0")) 8 | 9 | DB="${DB:-sqlite}" 10 | 11 | echo "SETTING UP DB $DB" 12 | 13 | export DB_DRIVER=$DB 14 | export DB_NAME=cypht_test 15 | 16 | if [ "$DB" = "sqlite" ]; then 17 | 18 | FILE=/tmp/test.db 19 | 20 | export DB_CONNECTION_TYPE=socket 21 | export DB_SOCKET=${FILE} 22 | 23 | cat ${SCRIPT_DIR}/data/schema_sqlite.sql | sqlite3 ${FILE} 24 | cat ${SCRIPT_DIR}/data/seed.sql | sqlite3 ${FILE} 25 | 26 | elif [ "$DB" = "mysql" ]; then 27 | # Load schema.sql 28 | mysql --defaults-extra-file=.github/tests/my.cnf cypht_test < ${SCRIPT_DIR}/data/schema.sql 29 | 30 | # Load seed.sql 31 | mysql --defaults-extra-file=.github/tests/my.cnf cypht_test < ${SCRIPT_DIR}/data/seed_mysql.sql 32 | 33 | elif [ "$DB" = "postgres" ]; then 34 | export DB_DRIVER=pgsql 35 | # Load schema.sql 36 | PGPASSWORD=cypht_test psql -h 127.0.0.1 -U cypht_test -d cypht_test -f ${SCRIPT_DIR}/data/schema_postgres.sql 37 | # Load seed.sql 38 | PGPASSWORD=cypht_test psql -h 127.0.0.1 -U cypht_test -d cypht_test -f ${SCRIPT_DIR}/data/seed_postgres.sql 39 | 40 | else 41 | echo "Database not supported in test: ${DB}" 42 | exit 1 43 | fi 44 | 45 | phpunit --bootstrap vendor/autoload.php --configuration ${SCRIPT_DIR}/phpunit.xml --testdox $@ 46 | -------------------------------------------------------------------------------- /modules/profiles/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var insert_sig = function(textarea, sig) { 4 | var tmpta = document.createElement('textarea'); 5 | tmpta.innerHTML = sig; 6 | sig = tmpta.value; 7 | if (document.selection) { 8 | textarea.focus(); 9 | var sel = document.selection.createRange(); 10 | sel.text = sig; 11 | } 12 | else if (textarea.selectionStart || textarea.selectionStart == '0') { 13 | var startPos = textarea.selectionStart; 14 | var endPos = textarea.selectionEnd; 15 | textarea.value = textarea.value.substring(0, startPos) + sig + textarea.value.substring(endPos, textarea.value.length); 16 | } 17 | else { 18 | textarea.value += textarea; 19 | } 20 | }; 21 | 22 | function profilesComposePageHandler() { 23 | $('.compose_sign').on("click", function() { 24 | var server_id = $('.compose_server').val(); 25 | if (profile_signatures[server_id]) { 26 | var ta = $('.ke-content', $('iframe').contents()); 27 | if (ta.length) { 28 | ta.html(ta.html() + profile_signatures[server_id].replace(/\n/g, '
')); 29 | } 30 | else { 31 | ta = $('#compose_body'); 32 | insert_sig(ta[0], profile_signatures[server_id]); 33 | } 34 | } else { 35 | Hm_Notices.show($('#sign_msg').val(), 'danger'); 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /modules/nasa/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nasa_disconnect = function(event) { 4 | if (!hm_delete_prompt()) { 5 | return false; 6 | } 7 | event.preventDefault(); 8 | Hm_Ajax.request( 9 | [{'name': 'hm_ajax_hook', 'value': 'ajax_nasa_disconnect'}, 10 | {'name': 'nasa_disconnect', 'value': true}], 11 | nasa_disconnect_result 12 | ); 13 | return false; 14 | }; 15 | 16 | var nasa_connect = function(event) { 17 | event.preventDefault(); 18 | var key = $('.nasa_api_key').val(); 19 | if (key.length) { 20 | Hm_Ajax.request( 21 | [{'name': 'hm_ajax_hook', 'value': 'ajax_nasa_connect'}, 22 | {'name': 'api_key', 'value': key}], 23 | nasa_connect_result 24 | ); 25 | } 26 | return false; 27 | }; 28 | 29 | var nasa_connect_result = function(res) { 30 | if (res.nasa_action_status) { 31 | $('.nasa_connect_inner_1').hide(); 32 | $('.nasa_connect_inner_2').show(); 33 | Hm_Folders.reload_folders(true); 34 | } 35 | }; 36 | 37 | var nasa_disconnect_result = function(res) { 38 | $('.nasa_api_key').val(''); 39 | $('.nasa_connect_inner_1').show(); 40 | $('.nasa_connect_inner_2').hide(); 41 | Hm_Folders.reload_folders(true); 42 | }; 43 | 44 | function nasaServersPageHandler() { 45 | $('.nasa_api_connect').on("click", nasa_connect); 46 | $('.nasa_api_disconnect').on("click", nasa_disconnect); 47 | } 48 | -------------------------------------------------------------------------------- /tests/phpunit/oauth2.php: -------------------------------------------------------------------------------- 1 | oauth2 = new Hm_Oauth2('client_id', 'secret', 'uri'); 12 | } 13 | /** 14 | * @preserveGlobalState disabled 15 | * @runInSeparateProcess 16 | */ 17 | public function test_request_authorization_url() { 18 | $res = $this->oauth2->request_authorization_url('url', 'scope', 'state', 'hint'); 19 | $this->assertEquals('url?response_type=code&scope=scope&state=state&approval_prompt=force&access_type=offline&client_id=client_id&redirect_uri=uri&login_hint=hint', $res); 20 | } 21 | /** 22 | * @preserveGlobalState disabled 23 | * @runInSeparateProcess 24 | */ 25 | public function test_refresh_token() { 26 | $res = $this->oauth2->refresh_token('url', 'refresh_token'); 27 | $this->assertEquals(array('unit' => 'test'), $res); 28 | } 29 | /** 30 | * @preserveGlobalState disabled 31 | * @runInSeparateProcess 32 | */ 33 | public function test_request_token() { 34 | $res = $this->oauth2->request_token('url', 'auth_code'); 35 | $this->assertEquals(array('unit' => 'test'), $res); 36 | } 37 | public function tearDown(): void { 38 | unset($this->oauth2); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/2fa.php: -------------------------------------------------------------------------------- 1 | env('APP_2FA_SECRET', ''), 22 | 23 | /* 24 | | 25 | | By default the generated secret will be 64 characters before being base32 26 | | encoded. To use a shorter secret that is easier to manually enter, set the 27 | | following to true. Note that if you change this setting after users have 28 | | enabled 2fa, they will have to use a backup code to login, then reset there 29 | | account in the authenticator app. 30 | */ 31 | '2fa_simple' => env('APP_2FA_SIMPLE', false) 32 | ]; 33 | -------------------------------------------------------------------------------- /tests/phpunit/transform.php: -------------------------------------------------------------------------------- 1 | assertEquals('{"foo":"YmFy"}', Hm_Transform::stringify(array('foo' => 'bar'))); 15 | $this->assertEquals('', Hm_Transform::stringify(NULL)); 16 | $this->assertEquals('asdf', Hm_Transform::stringify('asdf')); 17 | } 18 | /** 19 | * @preserveGlobalState disabled 20 | * @runInSeparateProcess 21 | */ 22 | public function test_unstringify() { 23 | $test = Hm_Transform::stringify(array('foo' => 'bar', 'baz' => array('test' => 'asdf'))); 24 | $this->assertEquals(array('foo' => 'bar', 'baz' => array('test' => 'asdf')), Hm_Transform::unstringify($test)); 25 | $this->assertFalse(Hm_Transform::unstringify(array())); 26 | $this->assertFalse(Hm_Transform::unstringify('asdf')); 27 | $this->assertEquals('asdf', Hm_Transform::unstringify('asdf', false, true)); 28 | $this->assertEquals(array('foo' => 'bar'), Hm_Transform::unstringify('a:1:{s:3:"foo";s:4:"YmFy";}')); 29 | $int_test = Hm_Transform::stringify(array('foo' => 1)); 30 | $this->assertEquals(array('foo' => 1), Hm_Transform::unstringify($int_test)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/create_account.php: -------------------------------------------------------------------------------- 1 | \n\n"); 16 | } 17 | 18 | /* debug mode has to be set to something or include files will die() */ 19 | define('DEBUG_MODE', false); 20 | 21 | /* determine current absolute path used for require statements */ 22 | define('APP_PATH', dirname(dirname(__FILE__)).'/'); 23 | define('VENDOR_PATH', APP_PATH.'vendor/'); 24 | define('WEB_ROOT', ''); 25 | 26 | /* get the framework */ 27 | require VENDOR_PATH.'autoload.php'; 28 | require APP_PATH.'lib/framework.php'; 29 | 30 | $environment = Hm_Environment::getInstance(); 31 | $environment->load(); 32 | 33 | /* get config object */ 34 | $config = new Hm_Site_Config_File(); 35 | /* set the default since and per_source values */ 36 | $environment->define_default_constants($config); 37 | 38 | /* check config for db auth */ 39 | if ($config->get('auth_type') != 'DB') { 40 | print("Incorrect usage\n\nThis script only works if DB auth is enabled in your site configuration\n\n"); 41 | exit(1); 42 | } 43 | 44 | $auth = new Hm_Auth_DB($config); 45 | 46 | if ($user && $pass) { 47 | $auth->create($user, $pass); 48 | } 49 | -------------------------------------------------------------------------------- /tests/selenium/remote_creds.example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This is an example config file to run the Selenium tests remotely, 4 | # specifically with BrowserStack (https://www.browserstack.com) 5 | 6 | # recipient E-mail for the send test 7 | RECIP='testuser@localhost.localdomain' 8 | 9 | # IMAP id (used to select the correct server in the servers test) 10 | IMAP_ID='2' 11 | 12 | # Get webdrivers 13 | from selenium import webdriver 14 | 15 | # Define the webdriver to use 16 | DRIVER = webdriver.Remote 17 | 18 | # Define the remote command. This format is specific to browserstack.com 19 | DRIVER_CMD='http://@hub.browserstack.com:80/wd/hub' 20 | 21 | # Set the browser and OS attributes. If this is a list of attribute 22 | # dictionaries, the test suites will be run across each set 23 | DESIRED_CAP = {'os': 'Windows', 'os_version': '7', 'browser': 'IE', 'browser_version': '11', 'resolution': '1920x1080' } 24 | 25 | # The base URL to run the tests against 26 | SITE_URL = 'https://some-public-site-running-cypht.com' 27 | 28 | # A valid username to login with 29 | USER = 'testuser' 30 | 31 | # A valid password for the username 32 | PASS = 'testpass' 33 | 34 | # A function that returns a webdriver object. 35 | def get_driver(cap): 36 | if not cap: 37 | cap = DESIRED_CAP 38 | return DRIVER(command_executor=DRIVER_CMD, desired_capabilities=cap) 39 | 40 | # A function called when all tests from one set 41 | # complete 42 | def success(driver): 43 | pass 44 | -------------------------------------------------------------------------------- /modules/desktop_notifications/site.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function() { 4 | window.addEventListener('new-message', (event) => { 5 | const row = event.detail.row; 6 | const content = $(row).find('.from').text() + ' - ' + $(row).find('.subject').text(); 7 | 8 | const pushNotification = () => Push.create(hm_trans("New Message"), { 9 | body: content, 10 | timeout: 10000, 11 | icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAIBJREFUWIXtlkEOgCAMBEdfpj/Hl+FB8GBiWoqEg7sJt7YzTXoAlL9nAfJMgXUmXAJVYAeOCeyjsO9sQOI6ypEvFdZrRomY4FEiJtgqiIp45zY3fAWu9d0Devu6N4mCTYHw9TrBboFWES+4WcASaQWHBZ4iUXAGsv4DEpCABBTlBOkR5VdJRFCfAAAAAElFTkSuQmCC', 12 | onClick: function () { window.focus(); this.close(); } 13 | }); 14 | 15 | if (Push.Permission.has()) { 16 | pushNotification(); 17 | } else { 18 | Push.Permission.request(pushNotification); 19 | } 20 | }); 21 | 22 | // refresh the unread messages state 23 | setInterval(() => { 24 | // undefined_undefined: load with no filter and no keyword 25 | new Hm_MessagesStore('unread', 1, 'undefined_undefined').load(true, true).then((store) => { 26 | store.newMessages.forEach((messageRow) => { 27 | triggerNewMessageEvent($(messageRow).data('uid'), $(messageRow)[0]); 28 | }); 29 | }); 30 | }, 60000); 31 | }); 32 | -------------------------------------------------------------------------------- /modules/sievefilters/README.md: -------------------------------------------------------------------------------- 1 | ## Sieve IMAP Filters 2 | 3 | Manage Sieve capable IMAP servers for message filtering. 4 | 5 | ### Email Filters with Cypht 6 | Cypht supports Sieve, enabling powerful email filtering: 7 | * Server-Side Filters: Work even when not logged in. 8 | * Spam Filtering: Filter emails based on sender, subject, or content. 9 | * Inbox Organization: Automatically sort emails into folders. 10 | * Custom Notifications: Create alerts for important emails. 11 | 12 | ### Enabling Sieve in Cypht 13 | Add sievefilters to CYPHT_MODULES in .env file: 14 | 15 | ``` 16 | CYPHT_MODULES="sievefilters" 17 | ``` 18 | 19 | ### Managing Filters 20 | 1. Go to Settings > Filters. 21 | 2. Select an email account and click Add Filter. 22 | 3. Enter filter details: 23 | * Priority: Define order of execution. 24 | * Conditions: Set criteria based on sender, subject, body, etc. 25 | 26 | ### Creating Custom Notifications 27 | 1. Go to Settings > Filters. 28 | 2. Select an email account and click Add Script. 29 | 3. Enter a name and Sieve code in the Filter script field: 30 | 31 | ``` 32 | require ["fileinto", "imap4flags", "notify"]; 33 | 34 | set "boss_email" "boss@example.com"; 35 | 36 | if address :is "from" "${boss_email}" { 37 | notify :message "You have a new email from your boss!" :options ["Important"] :method "mailto:your-email@example.com"; 38 | } 39 | ``` 40 | Enjoy managing your filters with Cypht and PHP Sieve Manager! 😄 41 | 42 | Uses https://packagist.org/packages/henrique-borba/php-sieve-manager 43 | -------------------------------------------------------------------------------- /scripts/delete_account.php: -------------------------------------------------------------------------------- 1 | \n\n"); 15 | } 16 | 17 | /* debug mode has to be set to something or include files will die() */ 18 | define('DEBUG_MODE', false); 19 | 20 | /* determine current absolute path used for require statements */ 21 | define('APP_PATH', dirname(dirname(__FILE__)).'/'); 22 | define('VENDOR_PATH', APP_PATH.'vendor/'); 23 | define('WEB_ROOT', ''); 24 | 25 | /* get the framework */ 26 | require VENDOR_PATH.'autoload.php'; 27 | require APP_PATH.'lib/framework.php'; 28 | 29 | $environment = Hm_Environment::getInstance(); 30 | $environment->load(); 31 | /* get config object */ 32 | $config = new Hm_Site_Config_File(); 33 | /* set the default since and per_source values */ 34 | $environment->define_default_constants($config); 35 | 36 | /* check config for db auth */ 37 | if ($config->get('auth_type') != 'DB') { 38 | die("Incorrect usage\n\nThis script only works if DB auth is enabled in your site configuration\n\n"); 39 | } 40 | 41 | $auth = new Hm_Auth_DB($config); 42 | if ($user) { 43 | if ($auth->delete($user)) { 44 | die("User deleted\n\n"); 45 | } 46 | else { 47 | die("An error occured\n\n"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /third_party/kindeditor/plugins/autoheight/autoheight.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * KindEditor - WYSIWYG HTML Editor for Internet 3 | * Copyright (C) 2006-2011 kindsoft.net 4 | * 5 | * @author Roddy 6 | * @site http://www.kindsoft.net/ 7 | * @licence http://www.kindsoft.net/license.php 8 | *******************************************************************************/ 9 | 10 | KindEditor.plugin('autoheight', function(K) { 11 | var self = this; 12 | 13 | if (!self.autoHeightMode) { 14 | return; 15 | } 16 | 17 | var minHeight; 18 | 19 | function hideScroll() { 20 | var edit = self.edit; 21 | var body = edit.doc.body; 22 | edit.iframe[0].scroll = 'no'; 23 | body.style.overflowY = 'hidden'; 24 | } 25 | 26 | function resetHeight() { 27 | var edit = self.edit; 28 | var body = edit.doc.body; 29 | edit.iframe.height(minHeight); 30 | self.resize(null, Math.max((K.IE ? body.scrollHeight : body.offsetHeight) + 76, minHeight)); 31 | } 32 | 33 | function init() { 34 | minHeight = K.removeUnit(self.height); 35 | 36 | self.edit.afterChange(resetHeight); 37 | hideScroll(); 38 | resetHeight(); 39 | } 40 | 41 | if (self.isCreated) { 42 | init(); 43 | } else { 44 | self.afterCreate(init); 45 | } 46 | }); 47 | 48 | /* 49 | * 如何实现真正的自动高度? 50 | * 修改编辑器高度之后,再次获取body内容高度时,最小值只会是当前iframe的设置高度,这样就导致高度只增不减。 51 | * 所以每次获取body内容高度之前,先将iframe的高度重置为最小高度,这样就能获取body的实际高度。 52 | * 由此就实现了真正的自动高度 53 | * 测试:chrome、firefox、IE9、IE8 54 | * */ 55 | -------------------------------------------------------------------------------- /modules/imap_folders/js_modules/route_handlers.js: -------------------------------------------------------------------------------- 1 | function applyFoldersPageHandlers() { 2 | $('#imap_server_folder').on("change", function() { 3 | $(this).parent().parent().submit(); 4 | }); 5 | $('.settings_subtitle').on("click", function() { return Hm_Utils.toggle_page_section($(this).data('target')); }); 6 | 7 | bindFoldersEventHandlers(); 8 | $(function() { 9 | const form = $('#form_folder_imap'); 10 | const autoSubmit = form.data('auto-submit'); 11 | if (autoSubmit && autoSubmit === 1) { 12 | form.submit(); 13 | } 14 | }) 15 | } 16 | 17 | function applyFoldersSubscriptionPageHandlers() { 18 | $('#imap_server_folder').on("change", function() { 19 | $(this).parent().parent().submit(); 20 | }); 21 | 22 | $('.subscribe_parent_folder').on("click", function() { return folder_page_folder_list('subscribe_parent_folder_select', 'subscribe_title', 'imap_parent_folder_link', '', 'subscribe_parent', true); }); 23 | $('.subscribe_parent_folder').trigger('click'); 24 | $($('.subscribe_parent_folder_select .imap_parent_folder_link')[0]).trigger('click'); 25 | const selected_imap_server = $('#imap_server_folder').val(); 26 | const email_folder_server = $(`.email_folders .imap_${selected_imap_server}_ .inner_list`); 27 | if (email_folder_server && $(email_folder_server[0]).children().length) { 28 | $($('.subscribe_parent_folder_select .imap_parent_folder_link')[0]).trigger('click'); 29 | } 30 | 31 | bindFoldersEventHandlers(); 32 | } -------------------------------------------------------------------------------- /modules/inline_message/setup.php: -------------------------------------------------------------------------------- 1 | array(), 22 | 'allowed_output' => array(), 23 | 'allowed_get' => array( 24 | ), 25 | 'allowed_post' => array( 26 | 'inline_message' => FILTER_VALIDATE_INT, 27 | 'inline_message_style' => FILTER_UNSAFE_RAW 28 | ) 29 | ); 30 | -------------------------------------------------------------------------------- /tests/phpunit/request_key.php: -------------------------------------------------------------------------------- 1 | assertEquals('fakefingerprint', Hm_Request_Key::generate()); 22 | $session = new Hm_Mock_Session(); 23 | $request = new Hm_Mock_Request('AJAX'); 24 | Hm_Request_Key::load($session, $request, false); 25 | $this->assertEquals('fakefingerprint', Hm_Request_Key::generate()); 26 | Hm_Request_Key::load($session, $request, true); 27 | $this->assertEquals('fakefingerprint', Hm_Request_Key::generate()); 28 | } 29 | /** 30 | * @preserveGlobalState disabled 31 | * @runInSeparateProcess 32 | */ 33 | public function test_key_generate() { 34 | $this->assertEquals('fakefingerprint', Hm_Request_Key::generate()); 35 | } 36 | /** 37 | * @preserveGlobalState disabled 38 | * @runInSeparateProcess 39 | */ 40 | public function test_key_validate() { 41 | $this->assertTrue(Hm_Request_Key::validate('fakefingerprint')); 42 | } 43 | public function tearDown(): void { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up PHP 16 | uses: shivammathur/setup-php@v2 17 | with: 18 | php-version: '8.1' 19 | extensions: curl, fileinfo, iconv, json, mbstring, openssl, session, pdo, sodium, xml, sqlite, pdo_mysql, pdo_pgsql, memcached, redis, gd, gnupg, imagick, bcmath, tidy, soap, xdebug 20 | tools: phpunit, composer 21 | ini-values: cgi.fix_pathinfo=1 22 | 23 | - name: Copy .env.example to .env 24 | run: cp .env.example .env 25 | 26 | - name: Install Composer dependencies 27 | run: composer install --no-dev --optimize-autoloader 28 | 29 | - name: Generate configuration files 30 | run: php scripts/config_gen.php 31 | 32 | - name: Create tarball 33 | run: tar czf ../cypht.tar.gz --exclude .git --exclude .gitignore --exclude .travis --exclude .travis.yml --exclude .github --exclude .coveralls.yml --exclude .env.example ./* ./.* 34 | 35 | - name: Upload release asset 36 | uses: actions/upload-release-asset@v1 37 | with: 38 | upload_url: ${{ github.event.release.upload_url }} 39 | asset_path: ../cypht.tar.gz 40 | asset_name: cypht.tar.gz 41 | asset_content_type: application/gzip 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.FINE_GRAINED_RELEASE_TOKEN }} 44 | -------------------------------------------------------------------------------- /modules/core/navigation/navbar.js: -------------------------------------------------------------------------------- 1 | $(() => { 2 | if(window.hm_mobile && hm_mobile()) { 3 | 4 | window.addEventListener('page-change', () => { 5 | hideMobileNavbar(); 6 | }); 7 | 8 | const menuToggle = ` 9 | 12 | ` 13 | 14 | $('.mobile .cypht-layout nav').before(menuToggle); 15 | 16 | $(document).on('click', '.cypht-layout .menu-toggle', showMobileNavbar); 17 | $(document).on('click', '.cypht-layout nav .menu-toggle', hideMobileNavbar) 18 | } else { 19 | $(document).on('click', '.menu-toggle', function() { 20 | $('.cypht-layout nav').toggleClass('collapsed'); 21 | if ($('.cypht-layout nav').hasClass('collapsed')) { 22 | document.documentElement.style.setProperty('--nav-size', 'var(--nav-collapsed-size)'); 23 | } else { 24 | document.documentElement.style.setProperty('--nav-size', 'var(--nav-expanded-size)'); 25 | } 26 | }) 27 | } 28 | }) 29 | 30 | function hideMobileNavbar() { 31 | $('.cypht-layout nav').css('transform', 'translateX(-120%)'); 32 | $('#cypht-main').css('max-height', 'unset'); 33 | $('#cypht-main').css('overflow', 'unset'); 34 | } 35 | 36 | function showMobileNavbar() { 37 | $('.cypht-layout nav').css('transform', 'translateX(0)'); 38 | $('#cypht-main').css('max-height', 'calc(100vh - 3.5rem)'); 39 | $('#cypht-main').css('overflow', 'hidden'); 40 | } -------------------------------------------------------------------------------- /scripts/update_password.php: -------------------------------------------------------------------------------- 1 | \n\n"); 16 | } 17 | 18 | /* debug mode has to be set to something or include files will die() */ 19 | define('DEBUG_MODE', false); 20 | 21 | /* determine current absolute path used for require statements */ 22 | define('APP_PATH', dirname(dirname(__FILE__)).'/'); 23 | define('VENDOR_PATH', APP_PATH.'vendor/'); 24 | define('WEB_ROOT', ''); 25 | 26 | /* get the framework */ 27 | require VENDOR_PATH.'autoload.php'; 28 | require APP_PATH.'lib/framework.php'; 29 | 30 | $environment = Hm_Environment::getInstance(); 31 | $environment->load(); 32 | /* get config object */ 33 | $config = new Hm_Site_Config_File(merge_config_files(APP_PATH.'config')); 34 | /* set the default since and per_source values */ 35 | $environment->define_default_constants($config); 36 | 37 | /* check config for db auth */ 38 | if ($config->get('auth_type') != 'DB') { 39 | die("Incorrect usage\n\nThis script only works if DB auth is enabled in your site configuration\n\n"); 40 | } 41 | 42 | $auth = new Hm_Auth_DB($config); 43 | if ($user && $pass) { 44 | if ($auth->change_pass($user, $pass)) { 45 | die("Password Updated\n\n"); 46 | } 47 | else { 48 | die("An error occured\n\n"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /third_party/kindeditor/plugins/plainpaste/plainpaste.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * KindEditor - WYSIWYG HTML Editor for Internet 3 | * Copyright (C) 2006-2011 kindsoft.net 4 | * 5 | * @author Roddy 6 | * @site http://www.kindsoft.net/ 7 | * @licence http://www.kindsoft.net/license.php 8 | *******************************************************************************/ 9 | 10 | KindEditor.plugin('plainpaste', function(K) { 11 | var self = this, name = 'plainpaste'; 12 | self.clickToolbar(name, function() { 13 | var lang = self.lang(name + '.'), 14 | html = '
' + 15 | '
' + lang.comment + '
' + 16 | '' + 17 | '
', 18 | dialog = self.createDialog({ 19 | name : name, 20 | width : 450, 21 | title : self.lang(name), 22 | body : html, 23 | yesBtn : { 24 | name : self.lang('yes'), 25 | click : function(e) { 26 | var html = textarea.val(); 27 | html = K.escape(html); 28 | html = html.replace(/ {2}/g, '  '); 29 | if (self.newlineTag == 'p') { 30 | html = html.replace(/^/, '

').replace(/$/, '

').replace(/\n/g, '

'); 31 | } else { 32 | html = html.replace(/\n/g, '
$&'); 33 | } 34 | self.insertHtml(html).hideDialog().focus(); 35 | } 36 | } 37 | }), 38 | textarea = K('textarea', dialog.div); 39 | textarea[0].focus(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/phpunit/environment.php: -------------------------------------------------------------------------------- 1 | load(); 21 | $cypht_dotenv = $environment->get('CYPHT_DOTENV'); 22 | $this->assertStringEndsWith(".env", $cypht_dotenv); 23 | } 24 | 25 | /** 26 | * @preserveGlobalState disabled 27 | * @runInSeparateProcess 28 | */ 29 | public function test_get_default_value() { 30 | $environment = Hm_Environment::getInstance(); 31 | $environment->load(); 32 | $undifined_env_data = $environment::get('APP_VERSION', "DEFAUL_VALUE"); 33 | $this->assertEquals('DEFAUL_VALUE', $undifined_env_data); 34 | } 35 | 36 | /** 37 | * @preserveGlobalState disabled 38 | * @runInSeparateProcess 39 | */ 40 | public function test_get_environment_variables() { 41 | $environment = Hm_Environment::getInstance(); 42 | $reflection = new ReflectionClass($environment); 43 | $method = $reflection->getMethod('get_environment_variables'); 44 | $method->setAccessible(true); 45 | $env_vars = $method->invoke($environment); 46 | $expected = array_merge($_ENV, $_SERVER); 47 | $this->assertEquals($expected, $env_vars); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/selenium/runner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from sys import exc_info 4 | from traceback import print_exception 5 | from creds import DESIRED_CAP, success 6 | 7 | GREEN = '\033[32m' 8 | RED = '\033[31m' 9 | END = '\033[0m' 10 | 11 | def run_tests(obj, tests): 12 | passed = 0 13 | for name in tests: 14 | func = getattr(obj, name) 15 | try: 16 | func() 17 | print('%s %sPASSED%s' % (name, GREEN, END)) 18 | passed += 1 19 | except Exception: 20 | print('%s %sFAILED%s' % (name, RED, END)) 21 | exc_type, exc_value, exc_traceback = exc_info() 22 | print_exception(exc_type, exc_value, exc_traceback) 23 | print('') 24 | print('%s%s of %s PASSED%s' % (GREEN, passed, len(tests), END)) 25 | print('') 26 | if (len(tests) > passed): 27 | print('%s%s of %s FAILED%s' % (RED, (len(tests) - passed), len(tests), END)) 28 | if obj.browser == 'safari': 29 | print("Safari unresolved failures, continuing...") 30 | else: 31 | obj.end() 32 | exit(1); 33 | else: 34 | success(obj.driver) 35 | obj.end() 36 | return True 37 | 38 | def get_tests(class_name): 39 | res = [] 40 | for method in class_name.__dict__: 41 | if not method.startswith('__'): 42 | res.append(method) 43 | return res 44 | 45 | def test_runner(class_name, tests=None): 46 | if not tests: 47 | tests = get_tests(class_name) 48 | if isinstance(DESIRED_CAP, list): 49 | for cap in DESIRED_CAP: 50 | run_tests(class_name(cap), tests) 51 | else: 52 | run_tests(class_name(), tests) 53 | -------------------------------------------------------------------------------- /tests/phpunit/api.php: -------------------------------------------------------------------------------- 1 | assertEquals(array('unit' => 'test'), $api->command('asdf')); 17 | } 18 | /** 19 | * @preserveGlobalState disabled 20 | * @runInSeparateProcess 21 | */ 22 | public function test_curl_setopt_post() { 23 | $api = new Hm_API_Curl(); 24 | $this->assertEquals(array('unit' => 'test'), $api->command('asdf', array(), array('foo' => 'bar'))); 25 | } 26 | /** 27 | * @preserveGlobalState disabled 28 | * @runInSeparateProcess 29 | */ 30 | public function test_curl_result() { 31 | $api = new Hm_API_Curl(); 32 | $this->assertEquals(array('unit' => 'test'), $api->command('asdf', array(), array('foo' => 'bar'))); 33 | Hm_Functions::$exec_res = NULL; 34 | $this->assertEquals(array(), $api->command('asdf', array(), array('foo' => 'bar'))); 35 | $api->format = 'binary'; 36 | Hm_Functions::$exec_res = 'foo'; 37 | $this->assertEquals('foo', $api->command('asdf', array(), 'bar')); 38 | } 39 | /** 40 | * @preserveGlobalState disabled 41 | * @runInSeparateProcess 42 | */ 43 | public function test_curl_custom() { 44 | $api = new Hm_API_Curl('xml'); 45 | $this->assertEquals('{"unit":"test"}', $api->command('asdf', array(), array(), 'foo', 'FOO')); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /modules/calendar/setup.php: -------------------------------------------------------------------------------- 1 | array( 22 | 'calendar', 23 | ), 24 | 'allowed_post' => array( 25 | 'event_title' => FILTER_UNSAFE_RAW, 26 | 'event_detail' => FILTER_UNSAFE_RAW, 27 | 'event_date' => FILTER_UNSAFE_RAW, 28 | 'event_time' => FILTER_UNSAFE_RAW, 29 | 'event_repeat' => FILTER_UNSAFE_RAW, 30 | 'delete_id' => FILTER_UNSAFE_RAW 31 | ), 32 | 'allowed_get' => array( 33 | 'date' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, 34 | 'view' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, 35 | 'action' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, 36 | ), 37 | ); 38 | -------------------------------------------------------------------------------- /modules/core/js_modules/[cash]/extend.js: -------------------------------------------------------------------------------- 1 | /* extend cash.js with some useful bits */ 2 | $.inArray = function(item, list) { 3 | for (var i in list) { 4 | if (list[i] === item) { 5 | return i; 6 | } 7 | } 8 | return -1; 9 | }; 10 | $.isEmptyObject = function(obj) { 11 | for (var key in obj) { 12 | if (obj.hasOwnProperty(key)) { 13 | return false; 14 | } 15 | } 16 | return true; 17 | }; 18 | $.fn.submit = function() { this[0].submit(); } 19 | $.fn.focus = function() { this[0].focus(); }; 20 | $.fn.serializeArray = function() { 21 | var parts; 22 | var res = []; 23 | var args = this.serialize().split('&'); 24 | for (var i in args) { 25 | parts = args[i].split('='); 26 | if (parts[0] && parts[1]) { 27 | res.push({'name': parts[0], 'value': parts[1]}); 28 | } 29 | } 30 | return res.map(function(x) {return {name: x.name, value: decodeURIComponent(x.value.replace(/\+/g, " "))}}); 31 | }; 32 | $.fn.sort = function(sort_function) { 33 | var list = []; 34 | for (var i=0, len=this.length; i < len; i++) { 35 | list.push(this[i]); 36 | } 37 | return $(list.sort(sort_function)); 38 | }; 39 | $.fn.fadeOut = function(timeout = 600) { 40 | return this.css("opacity", 0) 41 | .css("transition", `opacity ${timeout}ms`) 42 | }; 43 | 44 | $.fn.modal = function(action) { 45 | const modalElement = this[0]; 46 | if (modalElement) { 47 | const modal = new bootstrap.Modal(modalElement); 48 | if (action === 'show') { 49 | modal.show(); 50 | } else if (action === 'hide') { 51 | modal.hide(); 52 | } 53 | } 54 | }; -------------------------------------------------------------------------------- /tests/phpunit/site_file_config.php: -------------------------------------------------------------------------------- 1 | config = new Hm_User_Config_File($mock_config); 15 | } 16 | /** 17 | * @preserveGlobalState disabled 18 | * @runInSeparateProcess 19 | */ 20 | public function test_get_modules() { 21 | $config = new Hm_Site_Config_File(merge_config_files(APP_PATH.'tests/phpunit/data')); 22 | $this->assertFalse($config->get_modules()); 23 | $config->set('modules', 'asdf'); 24 | $this->assertEquals(array('asdf'), $config->get_modules()); 25 | } 26 | /** 27 | * @preserveGlobalState disabled 28 | * @runInSeparateProcess 29 | */ 30 | public function test_site_load() { 31 | $config = new Hm_Site_Config_File(merge_config_files(APP_PATH.'tests/phpunit/data')); 32 | $this->assertEquals(array('version' => VERSION, 'foo' => 'bar', 'default_setting_foo' => 'bar'), $config->dump()); 33 | } 34 | /** 35 | * @preserveGlobalState disabled 36 | * @runInSeparateProcess 37 | */ 38 | public function test_get_user_defaults() { 39 | $config = new Hm_Site_Config_File(merge_config_files(APP_PATH.'tests/phpunit/data')); 40 | $this->assertEquals(array('version' => VERSION, 'foo' => 'bar', 'default_setting_foo' => 'bar'), $config->dump()); 41 | } 42 | public function tearDown(): void { 43 | unset($this->config); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/phpunit/user_config_functions.php: -------------------------------------------------------------------------------- 1 | config = new Hm_User_Config_File($mock_config); 15 | } 16 | /** 17 | * @preserveGlobalState disabled 18 | * @runInSeparateProcess 19 | */ 20 | public function test_load_user_config_object() { 21 | /* TODO assertions */ 22 | $mock_config = new Hm_Mock_Config(); 23 | load_user_config_object($mock_config); 24 | $this->assertEquals('Hm_User_Config_File', get_class(load_user_config_object($mock_config))); 25 | $mock_config->set('user_config_type', 'DB'); 26 | $this->assertEquals('Hm_User_Config_DB', get_class(load_user_config_object($mock_config))); 27 | $mock_config->set('user_config_type', 'custom:Hm_Mock_Config'); 28 | $this->assertEquals('Hm_Mock_Config', get_class(load_user_config_object($mock_config))); 29 | } 30 | /** 31 | * @preserveGlobalState disabled 32 | * @runInSeparateProcess 33 | */ 34 | public function test_crypt_state() { 35 | $site_config = new Hm_Mock_Config(); 36 | $this->assertTrue(crypt_state($site_config)); 37 | $site_config->set('auth_type', 'IMAP'); 38 | $site_config->set('single_server_mode', true); 39 | $this->assertFalse(crypt_state($site_config)); 40 | } 41 | 42 | public function tearDown(): void { 43 | unset($this->config); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/phpunit/output_module.php: -------------------------------------------------------------------------------- 1 | output_mod = new Hm_Output_Test(array('foo' => 'bar', 'bar' => 'foo'), array('bar')); 14 | } 15 | /** 16 | * @preserveGlobalState disabled 17 | * @runInSeparateProcess 18 | */ 19 | public function test_output_content() { 20 | $this->output_mod->output_content('HTML5', array('Main' => false, 'Test' => 'Translated', 'interface_lang' => 'en', 'interface_direction' => 'ltr'), array()); 21 | $this->assertEquals('Main', $this->output_mod->trans('Main')); 22 | $this->assertEquals('Translated', $this->output_mod->trans('Test')); 23 | } 24 | /** 25 | * @preserveGlobalState disabled 26 | * @runInSeparateProcess 27 | */ 28 | public function test_trans() { 29 | $this->assertEquals('inbox', $this->output_mod->trans('inbox')); 30 | $this->assertEquals('Main', $this->output_mod->trans('Main')); 31 | } 32 | /** 33 | * @preserveGlobalState disabled 34 | * @runInSeparateProcess 35 | */ 36 | public function test_html_safe() { 37 | $this->assertEquals('<script>', $this->output_mod->html_safe('